From 69a2230e907152c3feb001d20e749f67e1fca6b1 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 9 Mar 2025 05:18:30 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/hardware_archive_service.rs | 125 ++++++++++-------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/src-tauri/src/services/hardware_archive_service.rs b/src-tauri/src/services/hardware_archive_service.rs index 736bd08..889ccef 100644 --- a/src-tauri/src/services/hardware_archive_service.rs +++ b/src-tauri/src/services/hardware_archive_service.rs @@ -18,64 +18,9 @@ pub async fn start_hardware_archive_service( // 1分待機 thread::sleep(Duration::from_secs(HISTORY_CAPACITY as u64)); - let cpu_avg = { - let cpu_hist = cpu_history.lock().unwrap(); - if cpu_hist.is_empty() { - 0.0 - } else { - cpu_hist.iter().sum::() / cpu_hist.len() as f32 - } - }; - - let cpu_max = cpu_history - .lock() - .unwrap() - .iter() - .cloned() - .fold(0.0, f32::max); - - let cpu_min = cpu_history - .lock() - .unwrap() - .iter() - .cloned() - .fold(100.0, f32::min); - - let memory_avg = { - let mem_hist = memory_history.lock().unwrap(); - if mem_hist.is_empty() { - 0.0 - } else { - mem_hist.iter().sum::() / mem_hist.len() as f32 - } - }; - - let memory_max = memory_history - .lock() - .unwrap() - .iter() - .cloned() - .fold(0.0, f32::max); - - let memory_min = memory_history - .lock() - .unwrap() - .iter() - .cloned() - .fold(100.0, f32::min); - // 現在時刻と平均値を用いてアーカイブデータを生成 - let cpu_data = structs::hardware_archive::HardwareData { - avg: cpu_avg, - max: cpu_max, - min: cpu_min, - }; - - let memory_data = structs::hardware_archive::HardwareData { - avg: memory_avg, - max: memory_max, - min: memory_min, - }; + let cpu_data = get_cpu_data(cpu_history.clone()); + let memory_data = get_memory_data(memory_history.clone()); let result = hardware_archive::insert(cpu_data, memory_data).await; if let Err(e) = result { @@ -106,3 +51,69 @@ pub async fn batch_delete_old_data(refresh_interval_days: u32) { } }); } + +fn get_cpu_data( + cpu_history: Arc>>, +) -> structs::hardware_archive::HardwareData { + let cpu_avg = { + let cpu_hist = cpu_history.lock().unwrap(); + if cpu_hist.is_empty() { + 0.0 + } else { + cpu_hist.iter().sum::() / cpu_hist.len() as f32 + } + }; + + let cpu_max = cpu_history + .lock() + .unwrap() + .iter() + .cloned() + .fold(0.0, f32::max); + + let cpu_min = cpu_history + .lock() + .unwrap() + .iter() + .cloned() + .fold(100.0, f32::min); + + structs::hardware_archive::HardwareData { + avg: cpu_avg, + max: cpu_max, + min: cpu_min, + } +} + +fn get_memory_data( + memory_history: Arc>>, +) -> structs::hardware_archive::HardwareData { + let memory_avg = { + let mem_hist = memory_history.lock().unwrap(); + if mem_hist.is_empty() { + 0.0 + } else { + mem_hist.iter().sum::() / mem_hist.len() as f32 + } + }; + + let memory_max = memory_history + .lock() + .unwrap() + .iter() + .cloned() + .fold(0.0, f32::max); + + let memory_min = memory_history + .lock() + .unwrap() + .iter() + .cloned() + .fold(100.0, f32::min); + + structs::hardware_archive::HardwareData { + avg: memory_avg, + max: memory_max, + min: memory_min, + } +} From d2665453b87914e0bd1aa3400e40c50c04ef1cca Mon Sep 17 00:00:00 2001 From: shm Date: Thu, 13 Mar 2025 12:58:40 +0900 Subject: [PATCH 2/7] Add: GPU_DATA_ARCHIVE --- src-tauri/src/commands/hardware.rs | 43 +++++- src-tauri/src/database/gpu_archive.rs | 34 +++++ src-tauri/src/database/migration.rs | 10 +- src-tauri/src/database/mod.rs | 1 + src-tauri/src/lib.rs | 8 ++ .../src/services/hardware_archive_service.rs | 124 +++++++++++++++++- src-tauri/src/services/nvidia_gpu_service.rs | 40 ++++++ src-tauri/src/structs/hardware_archive.rs | 11 ++ 8 files changed, 264 insertions(+), 7 deletions(-) create mode 100644 src-tauri/src/database/gpu_archive.rs diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index 74024c8..6d58434 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -26,6 +26,10 @@ pub struct AppState { pub gpu_history: Arc>>, pub process_cpu_histories: Arc>>>, pub process_memory_histories: Arc>>>, + #[allow(dead_code)] + pub nv_gpu_usage_histories: Arc>>>, + #[allow(dead_code)] + pub nv_gpu_temperature_histories: Arc>>>, } /// @@ -367,6 +371,8 @@ pub fn initialize_system( memory_history: Arc>>, process_cpu_histories: Arc>>>, process_memory_histories: Arc>>>, + nv_gpu_usage_histories: Arc>>>, + nv_gpu_temperature_histories: Arc>>>, ) { thread::spawn(move || loop { { @@ -430,8 +436,43 @@ pub fn initialize_system( memory_history.push_back(memory_usage); } } - } + // GPU使用率の履歴を保存 + { + let mut nv_gpu_usage_histories = nv_gpu_usage_histories.lock().unwrap(); + let mut nv_gpu_temperature_histories = + nv_gpu_temperature_histories.lock().unwrap(); + + let gpus = match nvapi::PhysicalGpu::enumerate() { + Ok(gpus) => gpus, + Err(_) => continue, + }; + + for (name, gpu) in gpus + .iter() + .map(|gpu| (gpu.full_name().unwrap_or("Unknown".to_string()), gpu)) + { + let usage_history = nv_gpu_usage_histories.entry(name.clone()).or_default(); + + if usage_history.len() >= HISTORY_CAPACITY { + usage_history.pop_front(); + } + usage_history + .push_back(nvidia_gpu_service::get_gpu_usage_from_physical_gpu(gpu)); + + let temperature_history = nv_gpu_temperature_histories + .entry(name.clone()) + .or_default(); + + if temperature_history.len() >= HISTORY_CAPACITY { + temperature_history.pop_front(); + } + temperature_history.push_back( + nvidia_gpu_service::get_gpu_temperature_from_physical_gpu(gpu), + ); + } + } + } thread::sleep(Duration::from_secs(SYSTEM_INFO_INIT_INTERVAL)); }); } diff --git a/src-tauri/src/database/gpu_archive.rs b/src-tauri/src/database/gpu_archive.rs new file mode 100644 index 0000000..2602711 --- /dev/null +++ b/src-tauri/src/database/gpu_archive.rs @@ -0,0 +1,34 @@ +use crate::structs; +use crate::utils; +use sqlx::sqlite::SqlitePool; + +pub async fn get_pool() -> Result { + let dir_path = utils::file::get_app_data_dir("hv-database.db"); + let database_url = format!("sqlite:{}", dir_path.to_str().unwrap()); + + let pool = SqlitePool::connect(&database_url).await?; + + Ok(pool) +} + +pub async fn insert(data: structs::hardware_archive::GpuData) -> Result<(), sqlx::Error> { + let pool = get_pool().await?; + + sqlx::query( + "INSERT INTO GPU_DATA_ARCHIVE (gpu_name, usage_avg, usage_max, usage_min, temperature_avg, temperature_max, temperature_min, timestamp) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + ).bind(data.name).bind(data.usage_avg).bind(data.usage_max).bind(data.usage_min).bind(data.temperature_avg).bind(data.temperature_max).bind(data.temperature_min).bind(chrono::Utc::now()).execute(&pool).await?; + + Ok(()) +} + +pub async fn delete_old_data(refresh_interval_days: u32) -> Result<(), sqlx::Error> { + let pool = get_pool().await?; + + sqlx::query("DELETE FROM GPU_DATA_ARCHIVE WHERE timestamp < $1") + .bind(chrono::Utc::now() - chrono::Duration::days(refresh_interval_days as i64)) + .execute(&pool) + .await?; + + Ok(()) +} diff --git a/src-tauri/src/database/migration.rs b/src-tauri/src/database/migration.rs index c1f4563..ccdd197 100644 --- a/src-tauri/src/database/migration.rs +++ b/src-tauri/src/database/migration.rs @@ -7,5 +7,13 @@ pub fn get_migrations() -> Vec { sql: "CREATE TABLE DATA_ARCHIVE (id INTEGER PRIMARY KEY, cpu_avg INTEGER, cpu_max INTEGER, cpu_min INTEGER, ram_avg INTEGER, ram_max INTEGER, ram_min INTEGER, timestamp DATETIME);", kind: MigrationKind::Up, - }] + }, + Migration { + version: 2, + description: "create_gpu_tables", + sql: + "CREATE TABLE GPU_DATA_ARCHIVE (id INTEGER PRIMARY KEY, gpu_name TEXT, usage_avg INTEGER, usage_max INTEGER, usage_min INTEGER, temperature_avg INTEGER, temperature_max INTEGER, temperature_min INTEGER, timestamp DATETIME);", + kind: MigrationKind::Up, + } + ] } diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs index d49927b..407043f 100644 --- a/src-tauri/src/database/mod.rs +++ b/src-tauri/src/database/mod.rs @@ -1,2 +1,3 @@ +pub mod gpu_archive; pub mod hardware_archive; pub mod migration; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0512251..8cb0141 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -36,6 +36,8 @@ pub fn run() { let gpu_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); let process_cpu_histories = Arc::new(Mutex::new(HashMap::new())); let process_memory_histories = Arc::new(Mutex::new(HashMap::new())); + let nv_gpu_usage_histories = Arc::new(Mutex::new(HashMap::new())); + let nv_gpu_temperature_histories = Arc::new(Mutex::new(HashMap::new())); let state = hardware::AppState { system: Arc::clone(&system), @@ -44,6 +46,8 @@ pub fn run() { gpu_history: Arc::clone(&gpu_history), process_cpu_histories: Arc::clone(&process_cpu_histories), process_memory_histories: Arc::clone(&process_memory_histories), + nv_gpu_usage_histories: Arc::clone(&nv_gpu_usage_histories), + nv_gpu_temperature_histories: Arc::clone(&nv_gpu_temperature_histories), }; let settings = app_state.settings.lock().unwrap().clone(); @@ -54,6 +58,8 @@ pub fn run() { memory_history.clone(), process_cpu_histories, process_memory_histories, + nv_gpu_usage_histories.clone(), + nv_gpu_temperature_histories.clone(), ); // ハードウェアアーカイブサービスの開始 @@ -62,6 +68,8 @@ pub fn run() { services::hardware_archive_service::start_hardware_archive_service( Arc::clone(&cpu_history), Arc::clone(&memory_history), + Arc::clone(&nv_gpu_usage_histories), + Arc::clone(&nv_gpu_temperature_histories), ), ); } diff --git a/src-tauri/src/services/hardware_archive_service.rs b/src-tauri/src/services/hardware_archive_service.rs index 889ccef..0e972ee 100644 --- a/src-tauri/src/services/hardware_archive_service.rs +++ b/src-tauri/src/services/hardware_archive_service.rs @@ -1,7 +1,7 @@ -use crate::{database::hardware_archive, structs}; +use crate::{database, structs}; use crate::{log_error, log_internal}; use std::{ - collections::VecDeque, + collections::{HashMap, VecDeque}, sync::{Arc, Mutex}, thread, time::Duration, @@ -12,6 +12,8 @@ const HISTORY_CAPACITY: usize = 60; pub async fn start_hardware_archive_service( cpu_history: Arc>>, memory_history: Arc>>, + nv_gpu_usage_histories: Arc>>>, + nv_gpu_temperature_histories: Arc>>>, ) { tokio::spawn(async move { loop { @@ -22,7 +24,7 @@ pub async fn start_hardware_archive_service( let cpu_data = get_cpu_data(cpu_history.clone()); let memory_data = get_memory_data(memory_history.clone()); - let result = hardware_archive::insert(cpu_data, memory_data).await; + let result = database::hardware_archive::insert(cpu_data, memory_data).await; if let Err(e) = result { log_error!( "Failed to insert hardware archive data", @@ -30,6 +32,22 @@ pub async fn start_hardware_archive_service( Some(e.to_string()) ); } + + let gpu_data_list = get_gpu_data( + nv_gpu_usage_histories.clone(), + nv_gpu_temperature_histories.clone(), + ); + + for gpu_data in gpu_data_list { + let result = database::gpu_archive::insert(gpu_data).await; + if let Err(e) = result { + log_error!( + "Failed to insert GPU hardware archive data", + "start_hardware_archive_service", + Some(e.to_string()) + ); + } + } } }); } @@ -39,8 +57,9 @@ pub async fn start_hardware_archive_service( /// pub async fn batch_delete_old_data(refresh_interval_days: u32) { tokio::task::spawn_blocking(move || { - let deletion_result = tokio::runtime::Handle::current() - .block_on(hardware_archive::delete_old_data(refresh_interval_days)); + let deletion_result = tokio::runtime::Handle::current().block_on( + database::hardware_archive::delete_old_data(refresh_interval_days), + ); if let Err(e) = deletion_result { log_error!( @@ -49,6 +68,18 @@ pub async fn batch_delete_old_data(refresh_interval_days: u32) { Some(e.to_string()) ); } + + let deletion_result = tokio::runtime::Handle::current().block_on( + database::gpu_archive::delete_old_data(refresh_interval_days), + ); + + if let Err(e) = deletion_result { + log_error!( + "Failed to delete old GPU hardware archive data", + "batch_delete_old_data", + Some(e.to_string()) + ); + } }); } @@ -117,3 +148,86 @@ fn get_memory_data( min: memory_min, } } + +fn get_gpu_data( + nv_gpu_usage_histories: Arc>>>, + nv_gpu_temperature_histories: Arc>>>, +) -> Vec { + let gpu_names = nv_gpu_usage_histories + .lock() + .unwrap() + .keys() + .cloned() + .collect::>(); + + let mut gpu_data: Vec = Vec::new(); + + for gpu_name in gpu_names { + let usage_avg = { + let usage_hist = nv_gpu_usage_histories.lock().unwrap(); + if usage_hist.is_empty() { + 0.0 + } else { + usage_hist.values().flat_map(|v| v.iter()).sum::() + / usage_hist.values().map(|v| v.len()).sum::() as f32 + } + }; + + let usage_max = nv_gpu_usage_histories + .lock() + .unwrap() + .values() + .flat_map(|v| v.iter()) + .cloned() + .fold(0.0, f32::max); + + let usage_min = nv_gpu_usage_histories + .lock() + .unwrap() + .values() + .flat_map(|v| v.iter()) + .cloned() + .fold(100.0, f32::min); + + let temperature_avg = { + let temperature_hist = nv_gpu_temperature_histories.lock().unwrap(); + if temperature_hist.is_empty() { + 0.0 + } else { + temperature_hist + .values() + .flat_map(|v| v.iter()) + .sum::() as f32 + / temperature_hist.values().map(|v| v.len()).sum::() as f32 + } + }; + + let temperature_max = nv_gpu_temperature_histories + .lock() + .unwrap() + .values() + .flat_map(|v| v.iter()) + .cloned() + .fold(i32::MIN, i32::max); + + let temperature_min = nv_gpu_temperature_histories + .lock() + .unwrap() + .values() + .flat_map(|v| v.iter()) + .cloned() + .fold(100, i32::min); + + gpu_data.push(structs::hardware_archive::GpuData { + name: gpu_name, + usage_avg: usage_avg, + usage_max: usage_max, + usage_min: usage_min, + temperature_avg: temperature_avg, + temperature_max: temperature_max, + temperature_min: temperature_min, + }); + } + + gpu_data +} diff --git a/src-tauri/src/services/nvidia_gpu_service.rs b/src-tauri/src/services/nvidia_gpu_service.rs index bf53757..562f8b3 100644 --- a/src-tauri/src/services/nvidia_gpu_service.rs +++ b/src-tauri/src/services/nvidia_gpu_service.rs @@ -284,3 +284,43 @@ pub async fn get_nvidia_gpu_info() -> Result, String> { nvapi::Status::Error.to_string() })? } + +/// +/// `PhysicalGpu` からGPU使用率を取得する +/// +pub fn get_gpu_usage_from_physical_gpu(gpu: &nvapi::PhysicalGpu) -> f32 { + let usage = match gpu.usages() { + Ok(usage) => usage, + Err(e) => { + log_error!("usages_failed", "get_gpu_usage", Some(e.to_string())); + return 0.0; + } + }; + + if let Some(gpu_usage) = usage.get(&UtilizationDomain::Graphics) { + let usage_f32 = gpu_usage.0 as f32 / 100.0; + return usage_f32; + } + + 0.0 +} + +/// +/// `PhysicalGpu` からGPU温度を取得する +/// +pub fn get_gpu_temperature_from_physical_gpu(gpu: &nvapi::PhysicalGpu) -> i32 { + let thermal_settings = gpu.thermal_settings(None).map_err(|e| { + log_warn!( + "thermal_settings_failed", + "get_gpu_temperature", + Some(&format!("{:?}", e)) + ); + 0 + }); + + if let Ok(thermal_settings) = thermal_settings { + return thermal_settings[0].current_temperature.0; + } + + 0 +} diff --git a/src-tauri/src/structs/hardware_archive.rs b/src-tauri/src/structs/hardware_archive.rs index ff56612..7853123 100644 --- a/src-tauri/src/structs/hardware_archive.rs +++ b/src-tauri/src/structs/hardware_archive.rs @@ -15,3 +15,14 @@ pub struct HardwareData { pub max: f32, pub min: f32, } + +#[derive(Serialize, Deserialize, Debug)] +pub struct GpuData { + pub name: String, + pub usage_avg: f32, + pub usage_max: f32, + pub usage_min: f32, + pub temperature_avg: f32, + pub temperature_max: i32, + pub temperature_min: i32, +} From b5d34c9c9846499786461ff84895b6ebab2ebfab Mon Sep 17 00:00:00 2001 From: shm Date: Thu, 13 Mar 2025 13:48:23 +0900 Subject: [PATCH 3/7] =?UTF-8?q?Fix:=20GpuData=E6=A7=8B=E9=80=A0=E4=BD=93?= =?UTF-8?q?=E3=81=AE=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB=E3=83=89=E5=90=8D?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/database/gpu_archive.rs | 2 +- src-tauri/src/services/hardware_archive_service.rs | 14 +++++++------- src-tauri/src/structs/hardware_archive.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/database/gpu_archive.rs b/src-tauri/src/database/gpu_archive.rs index 2602711..5cdb3a0 100644 --- a/src-tauri/src/database/gpu_archive.rs +++ b/src-tauri/src/database/gpu_archive.rs @@ -17,7 +17,7 @@ pub async fn insert(data: structs::hardware_archive::GpuData) -> Result<(), sqlx sqlx::query( "INSERT INTO GPU_DATA_ARCHIVE (gpu_name, usage_avg, usage_max, usage_min, temperature_avg, temperature_max, temperature_min, timestamp) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", - ).bind(data.name).bind(data.usage_avg).bind(data.usage_max).bind(data.usage_min).bind(data.temperature_avg).bind(data.temperature_max).bind(data.temperature_min).bind(chrono::Utc::now()).execute(&pool).await?; + ).bind(data.gpu_name).bind(data.usage_avg).bind(data.usage_max).bind(data.usage_min).bind(data.temperature_avg).bind(data.temperature_max).bind(data.temperature_min).bind(chrono::Utc::now()).execute(&pool).await?; Ok(()) } diff --git a/src-tauri/src/services/hardware_archive_service.rs b/src-tauri/src/services/hardware_archive_service.rs index 0e972ee..397ac36 100644 --- a/src-tauri/src/services/hardware_archive_service.rs +++ b/src-tauri/src/services/hardware_archive_service.rs @@ -219,13 +219,13 @@ fn get_gpu_data( .fold(100, i32::min); gpu_data.push(structs::hardware_archive::GpuData { - name: gpu_name, - usage_avg: usage_avg, - usage_max: usage_max, - usage_min: usage_min, - temperature_avg: temperature_avg, - temperature_max: temperature_max, - temperature_min: temperature_min, + gpu_name, + usage_avg, + usage_max, + usage_min, + temperature_avg, + temperature_max, + temperature_min, }); } diff --git a/src-tauri/src/structs/hardware_archive.rs b/src-tauri/src/structs/hardware_archive.rs index 7853123..d983350 100644 --- a/src-tauri/src/structs/hardware_archive.rs +++ b/src-tauri/src/structs/hardware_archive.rs @@ -18,7 +18,7 @@ pub struct HardwareData { #[derive(Serialize, Deserialize, Debug)] pub struct GpuData { - pub name: String, + pub gpu_name: String, pub usage_avg: f32, pub usage_max: f32, pub usage_min: f32, From 0ba0d69880a1d38dff7891d704eed4ec5d3f7aee Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 15 Mar 2025 21:01:17 +0900 Subject: [PATCH 4/7] Add GPU insights feature with child menu and data handling --- src/atom/ui.ts | 3 + .../charts/insights/InsightChart.tsx | 61 ++++- .../charts/insights/useInsightChart.ts | 115 ++++++--- src/i18n/en.json | 8 +- src/i18n/ja.json | 8 +- src/template/Insights.tsx | 226 +++++++++++++++++- src/template/SideMenu.tsx | 28 ++- src/types/chart.ts | 18 ++ src/types/ui.ts | 4 + 9 files changed, 433 insertions(+), 38 deletions(-) diff --git a/src/atom/ui.ts b/src/atom/ui.ts index d6cf2d9..2df059e 100644 --- a/src/atom/ui.ts +++ b/src/atom/ui.ts @@ -1,5 +1,8 @@ +import type { InsightChildMenuType } from "@/types/ui"; import { atom } from "jotai"; export const modalAtoms = { showSettingsModal: atom(false), }; + +export const insightMenuAtom = atom("main"); diff --git a/src/components/charts/insights/InsightChart.tsx b/src/components/charts/insights/InsightChart.tsx index 14002fb..4457839 100644 --- a/src/components/charts/insights/InsightChart.tsx +++ b/src/components/charts/insights/InsightChart.tsx @@ -3,7 +3,11 @@ import { SingleLineChart } from "@/components/charts/LineChart"; import type { ChartConfig } from "@/components/ui/chart"; import type { archivePeriods } from "@/consts"; import type { HardwareType } from "@/rspc/bindings"; -import type { ChartDataType, DataStats } from "@/types/hardwareDataType"; +import type { + ChartDataType, + DataStats, + HardwareDataType, +} from "@/types/hardwareDataType"; import { useInsightChart } from "./useInsightChart"; export const InsightChart = ({ @@ -58,3 +62,58 @@ export const InsightChart = ({ ); }; + +export const GpuInsightChart = ({ + dataType, + period, + dataStats, + offset, +}: { + dataType: Exclude; + period: (typeof archivePeriods)[number]; + dataStats: DataStats; + offset: number; +}) => { + const { settings } = useSettingsAtom(); + const { labels, chartData } = useInsightChart({ + hardwareType: "gpu", + dataStats, + dataType, + period, + offset, + gpuName: "NVIDIA GeForce RTX 3060 Ti", + }); + + const chartConfig: Record = { + cpu: { + label: "CPU", + color: `rgb(${settings.lineGraphColor.cpu})`, + }, + memory: { + label: "RAM", + color: `rgb(${settings.lineGraphColor.memory})`, + }, + gpu: { + label: "GPU", + color: `rgb(${settings.lineGraphColor.gpu})`, + }, + } satisfies ChartConfig; + + return ( +
+ +
+ ); +}; diff --git a/src/components/charts/insights/useInsightChart.ts b/src/components/charts/insights/useInsightChart.ts index a91d624..11e0d89 100644 --- a/src/components/charts/insights/useInsightChart.ts +++ b/src/components/charts/insights/useInsightChart.ts @@ -1,9 +1,13 @@ import { type archivePeriods, chartConfig } from "@/consts"; import { sqlitePromise } from "@/lib/sqlite"; import type { HardwareType } from "@/rspc/bindings"; -import type { DataArchive } from "@/types/chart"; -import type { DataStats } from "@/types/hardwareDataType"; -import { useEffect, useMemo, useState } from "react"; +import type { + DataArchive, + GpuDataArchive, + SingleDataArchive, +} from "@/types/chart"; +import type { DataStats, HardwareDataType } from "@/types/hardwareDataType"; +import { useCallback, useEffect, useMemo, useState } from "react"; // 各タイプに合わせた集計関数の定義 const aggregatorMap: Record number> = { @@ -12,20 +16,6 @@ const aggregatorMap: Record number> = { min: (vals) => Math.min(...vals), }; -const getData = async ({ - endAt, - period, -}: { endAt: Date; period: (typeof archivePeriods)[number] }): Promise< - DataArchive[] -> => { - const adjustedEndAt = new Date( - endAt.getTime() - chartConfig.archiveUpdateIntervalMilSec, - ); - const sql: string = `SELECT * FROM DATA_ARCHIVE WHERE timestamp BETWEEN '${new Date(adjustedEndAt.getTime() - period * 60 * 1000).toISOString()}' AND '${adjustedEndAt.toISOString()}'`; - - return await (await sqlitePromise).load(sql); -}; - const getDataArchiveKey = ( hardwareType: Exclude, dataStats: DataStats, @@ -49,18 +39,58 @@ const getDataArchiveKey = ( return keyMap[hardwareType][dataStats]; }; -export const useInsightChart = ({ - hardwareType, - dataStats, - period, - offset, -}: { +const getGpuDataArchiveKey = ( + dataType: Exclude, + dataStats: DataStats, +): keyof GpuDataArchive => { + const keyMap: Record< + Exclude, + Record + > = { + usage: { + avg: "usage_avg", + max: "usage_max", + min: "usage_min", + }, + temp: { + avg: "temperature_avg", + max: "temperature_max", + min: "temperature_min", + }, + }; + + return keyMap[dataType][dataStats]; +}; + +type UseInsightChartGpuProps = { + hardwareType: Extract; + dataStats: DataStats; + dataType: Exclude; + period: (typeof archivePeriods)[number]; + offset: number; + gpuName: string; +}; + +type UseInsightChartProps = { hardwareType: Exclude; dataStats: DataStats; period: (typeof archivePeriods)[number]; offset: number; -}) => { - const [data, setData] = useState>([]); +}; + +export const useInsightChart = ( + props: UseInsightChartGpuProps | UseInsightChartProps, +) => { + const { hardwareType, dataStats, period, offset } = props; + const gpuName = + hardwareType === "gpu" ? (props as UseInsightChartGpuProps).gpuName : ""; + const dataType = + hardwareType === "gpu" + ? (props as UseInsightChartGpuProps).dataType + : undefined; + + const [data, setData] = useState>([]); + const step = { 10: 1, @@ -78,9 +108,34 @@ export const useInsightChart = ({ return new Date(Date.now() - offset * step); }, [offset, step]); + const getData = useCallback(async (): Promise => { + const adjustedEndAt = new Date( + endAt.getTime() - chartConfig.archiveUpdateIntervalMilSec, + ); + const startTime = new Date(adjustedEndAt.getTime() - period * 60 * 1000); + + const sql = + hardwareType === "gpu" + ? `SELECT ${getGpuDataArchiveKey( + // biome-ignore lint/style/noNonNullAssertion: + dataType!, + dataStats, + )} as value, timestamp + FROM GPU_DATA_ARCHIVE + WHERE gpu_name = '${gpuName}' + AND timestamp BETWEEN '${startTime.toISOString()}' + AND '${adjustedEndAt.toISOString()}'` + : `SELECT ${getDataArchiveKey(hardwareType, dataStats)} as value, timestamp + FROM DATA_ARCHIVE + WHERE timestamp BETWEEN '${startTime.toISOString()}' + AND '${adjustedEndAt.toISOString()}'`; + + return await (await sqlitePromise).load(sql); + }, [endAt, hardwareType, period, dataStats, gpuName, dataType]); + useEffect(() => { const updateData = () => { - getData({ endAt, period }).then((data) => setData(data)); + getData().then((data) => setData(data)); }; updateData(); @@ -90,7 +145,7 @@ export const useInsightChart = ({ chartConfig.archiveUpdateIntervalMilSec, ); return () => clearInterval(intervalId); - }, [period, endAt]); + }, [getData]); const startTime = useMemo( () => new Date(endAt.getTime() - period * 60 * 1000), @@ -114,14 +169,12 @@ export const useInsightChart = ({ if (!acc[bucketTimestamp]) { acc[bucketTimestamp] = []; } - acc[bucketTimestamp].push( - record[getDataArchiveKey(hardwareType, dataStats)], - ); + acc[bucketTimestamp].push(record.value); return acc; }, {} as Record, ); - }, [data, step, dataStats, hardwareType]); + }, [data, step]); const aggregateFn = aggregatorMap[dataStats]; diff --git a/src/i18n/en.json b/src/i18n/en.json index 0e5f638..b8f42df 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -61,7 +61,13 @@ "name": "Usage" }, "insights": { - "name": "Insights" + "name": "Insights", + "main": { + "title": "Main" + }, + "gpu": { + "title": "GPU" + } }, "settings": { "name": "Settings", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 7d1df92..6b67bf4 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -61,7 +61,13 @@ "name": "ハードウェア使用率" }, "insights": { - "name": "インサイト" + "name": "インサイト", + "main": { + "title": "メイン" + }, + "gpu": { + "title": "GPU" + } }, "settings": { "name": "設定", diff --git a/src/template/Insights.tsx b/src/template/Insights.tsx index 84bbcb5..9ab6cd6 100644 --- a/src/template/Insights.tsx +++ b/src/template/Insights.tsx @@ -1,4 +1,8 @@ -import { InsightChart } from "@/components/charts/insights/InsightChart"; +import { insightMenuAtom } from "@/atom/ui"; +import { + GpuInsightChart, + InsightChart, +} from "@/components/charts/insights/InsightChart"; import { Select, SelectContent, @@ -8,7 +12,13 @@ import { } from "@/components/ui/select"; import { archivePeriods } from "@/consts"; import { useTauriStore } from "@/hooks/useTauriStore"; -import type { ChartDataType, DataStats } from "@/types/hardwareDataType"; +import type { + ChartDataType, + DataStats, + HardwareDataType, +} from "@/types/hardwareDataType"; +import type { InsightChildMenuType } from "@/types/ui"; +import { useAtom } from "jotai"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { type JSX, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -65,7 +75,7 @@ const SelectPeriod = ({ ); }; -export const Insights = () => { +const MainInsights = () => { const { t } = useTranslation(); const [periodAvgCPU, setPeriodAvgCPU] = useTauriStore< (typeof archivePeriods)[number] | null @@ -230,3 +240,213 @@ export const Insights = () => { ); }; + +const GPUInsights = () => { + const { t } = useTranslation(); + const [periodAvgGpuUsage, setPeriodAvgGpuUsage] = useTauriStore< + (typeof archivePeriods)[number] | null + >("periodAvgGpuUsage", null); + const [periodAvgGpuTemperature, setPeriodAvgGpuTemperature] = useTauriStore< + (typeof archivePeriods)[number] | null + >("periodAvgGpuTemperature", null); + const [periodMaxGpuUsage, setPeriodMaxGpuUsage] = useTauriStore< + (typeof archivePeriods)[number] | null + >("periodMaxGpuUsage", null); + const [periodMaxGpuTemperature, setPeriodMaxGpuTemperature] = useTauriStore< + (typeof archivePeriods)[number] | null + >("periodMaxGpuTemperature", null); + const [periodMinGpuUsage, setPeriodMinGpuUsage] = useTauriStore< + (typeof archivePeriods)[number] | null + >("periodMinGpuUsage", null); + const [periodMinGpuTemperature, setPeriodMinGpuTemperature] = useTauriStore< + (typeof archivePeriods)[number] | null + >("periodMinGpuTemperature", null); + + const periods: Record<(typeof archivePeriods)[number], string> = { + "10": `10 ${t("shared.time.minutes")}`, + "30": `30 ${t("shared.time.minutes")}`, + "60": `1 ${t("shared.time.hours")}`, + "180": `3 ${t("shared.time.hours")}`, + "720": `12 ${t("shared.time.hours")}`, + "1440": `1 ${t("shared.time.days")}`, + "10080": `7 ${t("shared.time.days")}`, + "20160": `14 ${t("shared.time.days")}`, + "43200": `30 ${t("shared.time.days")}`, + }; + + const options = archivePeriods.map((period) => ({ + label: periods[period], + value: period, + })); + + const selections = [ + periodAvgGpuUsage, + periodAvgGpuTemperature, + periodMaxGpuUsage, + periodMaxGpuTemperature, + periodMinGpuUsage, + periodMinGpuTemperature, + ]; + + const chartData: { + type: Exclude; + stats: DataStats; + period: [ + (typeof archivePeriods)[number] | null, + (newValue: (typeof archivePeriods)[number] | null) => Promise, + ]; + }[] = [ + { + type: "usage", + stats: "avg", + period: [periodAvgGpuUsage, setPeriodAvgGpuUsage], + }, + { + type: "temp", + stats: "avg", + period: [periodAvgGpuTemperature, setPeriodAvgGpuTemperature], + }, + { + type: "usage", + stats: "max", + period: [periodMaxGpuUsage, setPeriodMaxGpuUsage], + }, + { + type: "temp", + stats: "max", + period: [periodMaxGpuTemperature, setPeriodMaxGpuTemperature], + }, + { + type: "usage", + stats: "min", + period: [periodMinGpuUsage, setPeriodMinGpuUsage], + }, + { + type: "temp", + stats: "min", + period: [periodMinGpuTemperature, setPeriodMinGpuTemperature], + }, + ]; + + return ( +
+
+ s === selections[0]) + ? periodAvgGpuUsage + : null + } + onChange={(v) => { + setPeriodAvgGpuUsage(v); + setPeriodAvgGpuTemperature(v); + setPeriodMaxGpuUsage(v); + setPeriodMaxGpuTemperature(v); + setPeriodMinGpuUsage(v); + setPeriodMinGpuTemperature(v); + }} + showDefaultOption={!selections.every((s) => s === selections[0])} + /> +
+ +
+ {chartData.map((data) => { + const { type, stats, period } = data; + const [periodData, setPeriodData] = period; + const [offset, setOffset] = useState(0); + const [intervalId, setIntervalId] = useState( + null, + ); + + const handleMouseDown = (increment: number) => { + if (intervalId) return; + const id = setInterval(() => { + setOffset((prev) => Math.max(0, prev + increment)); + }, 100); + setIntervalId(id); + }; + + const handleMouseUp = () => { + if (intervalId) { + clearInterval(intervalId); + setIntervalId(null); + } + }; + + const dataType: Record<"temp" | "usage", "usage" | "temperature"> = { + usage: "usage", + temp: "temperature", + }; + + const dataTypeKeys: "usage" | "temperature" = dataType[data.type]; + + return ( + periodData && ( + + <> +
+

+ {t(`shared.${dataTypeKeys}`)} ({t(`shared.${data.stats}`)} + ) +

+ +
+ +
+ + + +
+ +
+ ) + ); + })} +
+
+ ); +}; + +export const Insights = () => { + const [displayTarget] = useAtom(insightMenuAtom); + + const InsightsChild: Record = { + main: , + gpu: , + }; + + return InsightsChild[displayTarget]; +}; diff --git a/src/template/SideMenu.tsx b/src/template/SideMenu.tsx index cd632ce..9e12d48 100644 --- a/src/template/SideMenu.tsx +++ b/src/template/SideMenu.tsx @@ -1,5 +1,6 @@ +import { insightMenuAtom } from "@/atom/ui"; import { useTauriStore } from "@/hooks/useTauriStore"; -import type { SelectedDisplayType } from "@/types/ui"; +import { type SelectedDisplayType, insightChildMenu } from "@/types/ui"; import { CaretDoubleLeft, CaretDoubleRight } from "@phosphor-icons/react"; import { atom, useAtom } from "jotai"; import { memo, useCallback, useEffect, useMemo } from "react"; @@ -92,6 +93,9 @@ export const SideMenu = memo(() => { > {menuTitles[type]} + {type === "insights" && displayTarget === type && ( + + )} ) ); @@ -133,3 +137,25 @@ export const SideMenu = memo(() => { ) ); }); + +const InsightChildMenu = memo(() => { + const { t } = useTranslation(); + const [displayTarget, setDisplayTarget] = useAtom(insightMenuAtom); + + return insightChildMenu.map((menuType) => ( +
  • + +
  • + )); +}); diff --git a/src/types/chart.ts b/src/types/chart.ts index 53c4362..43abf14 100644 --- a/src/types/chart.ts +++ b/src/types/chart.ts @@ -8,3 +8,21 @@ export type DataArchive = { ram_min: number; timestamp: number; }; + +export type GpuDataArchive = { + id: number; + gpu_name: string; + usage_avg: number; + usage_max: number; + usage_min: number; + temperature_avg: number; + temperature_max: number; + temperature_min: number; + timestamp: number; +}; + +export type SingleDataArchive = { + id: number; + value: number; + timestamp: number; +}; diff --git a/src/types/ui.ts b/src/types/ui.ts index 101df76..8b9ac68 100644 --- a/src/types/ui.ts +++ b/src/types/ui.ts @@ -3,3 +3,7 @@ export type SelectedDisplayType = | "usage" | "insights" | "settings"; + +export const insightChildMenu = ["main", "gpu"] as const; + +export type InsightChildMenuType = (typeof insightChildMenu)[number]; From c2ee085d868e7f398d7ee0c67daef5022c1625c6 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 16 Mar 2025 13:22:35 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Fix:=20GPU=E4=BD=BF=E7=94=A8=E7=8E=87?= =?UTF-8?q?=E3=81=AE=E8=A8=88=E7=AE=97=E3=81=8B=E3=82=89100=E3=81=A7?= =?UTF-8?q?=E3=81=AE=E9=99=A4=E7=AE=97=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/services/nvidia_gpu_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/src/services/nvidia_gpu_service.rs b/src-tauri/src/services/nvidia_gpu_service.rs index 562f8b3..c86b0b8 100644 --- a/src-tauri/src/services/nvidia_gpu_service.rs +++ b/src-tauri/src/services/nvidia_gpu_service.rs @@ -298,7 +298,7 @@ pub fn get_gpu_usage_from_physical_gpu(gpu: &nvapi::PhysicalGpu) -> f32 { }; if let Some(gpu_usage) = usage.get(&UtilizationDomain::Graphics) { - let usage_f32 = gpu_usage.0 as f32 / 100.0; + let usage_f32 = gpu_usage.0 as f32; return usage_f32; } From 35c207f95d0a6a186aaf916d492593ffa751139a Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 16 Mar 2025 14:44:21 +0900 Subject: [PATCH 6/7] Add: GPU Insight --- package-lock.json | 198 ++++++++---------- package.json | 1 + src/atom/ui.ts | 3 - .../charts/insights/InsightChart.tsx | 4 +- src/components/ui/tabs.tsx | 64 ++++++ src/i18n/ja.json | 2 +- src/template/Insights.tsx | 74 +++++-- src/template/SideMenu.tsx | 28 +-- 8 files changed, 220 insertions(+), 154 deletions(-) create mode 100644 src/components/ui/tabs.tsx diff --git a/package-lock.json b/package-lock.json index 7ca2e3e..e775455 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-visually-hidden": "^1.1.2", "@tailwindcss/vite": "^4.0.8", @@ -685,6 +686,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -701,6 +703,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -717,6 +720,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -733,6 +737,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -749,6 +754,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -765,6 +771,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -781,6 +788,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -797,6 +805,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -813,6 +822,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -829,6 +839,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -845,6 +856,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -861,6 +873,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -877,6 +890,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -893,6 +907,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -909,6 +924,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -925,6 +941,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -941,6 +958,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -957,6 +975,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -973,6 +992,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -989,6 +1009,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1005,6 +1026,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1021,6 +1043,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1037,6 +1060,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1053,6 +1077,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1069,6 +1094,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1922,6 +1948,36 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz", + "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz", @@ -2109,6 +2165,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2122,6 +2179,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2135,6 +2193,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2148,6 +2207,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2161,6 +2221,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2174,6 +2235,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2187,6 +2249,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2200,6 +2263,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2213,6 +2277,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2226,6 +2291,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2239,6 +2305,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2252,6 +2319,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2265,6 +2333,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2278,6 +2347,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2291,6 +2361,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2304,6 +2375,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2317,6 +2389,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2330,6 +2403,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2343,6 +2417,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2833,27 +2908,6 @@ "@tauri-apps/api": "^2.0.0" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@testing-library/jest-dom": { "version": "6.6.3", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", @@ -2938,14 +2992,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3058,13 +3104,14 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -3074,7 +3121,7 @@ "version": "19.0.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -3084,7 +3131,7 @@ "version": "19.0.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -3459,24 +3506,6 @@ "node": ">=12" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -3804,14 +3833,6 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -3944,6 +3965,7 @@ "version": "0.24.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -4062,6 +4084,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -4831,17 +4854,6 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "lz-string": "bin/bin.js" - } - }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -4973,6 +4985,7 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, "funding": [ { "type": "github", @@ -5085,12 +5098,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5115,36 +5130,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5243,14 +5228,6 @@ } } }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -5423,6 +5400,7 @@ "version": "4.34.8", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -5547,6 +5525,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5877,7 +5856,7 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -5891,7 +5870,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -5994,6 +5973,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz", "integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.24.2", diff --git a/package.json b/package.json index 441f7b0..31d6577 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@radix-ui/react-slider": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-visually-hidden": "^1.1.2", "@tailwindcss/vite": "^4.0.8", diff --git a/src/atom/ui.ts b/src/atom/ui.ts index 2df059e..d6cf2d9 100644 --- a/src/atom/ui.ts +++ b/src/atom/ui.ts @@ -1,8 +1,5 @@ -import type { InsightChildMenuType } from "@/types/ui"; import { atom } from "jotai"; export const modalAtoms = { showSettingsModal: atom(false), }; - -export const insightMenuAtom = atom("main"); diff --git a/src/components/charts/insights/InsightChart.tsx b/src/components/charts/insights/InsightChart.tsx index 4457839..d5eaba1 100644 --- a/src/components/charts/insights/InsightChart.tsx +++ b/src/components/charts/insights/InsightChart.tsx @@ -68,11 +68,13 @@ export const GpuInsightChart = ({ period, dataStats, offset, + gpuName, }: { dataType: Exclude; period: (typeof archivePeriods)[number]; dataStats: DataStats; offset: number; + gpuName: string; }) => { const { settings } = useSettingsAtom(); const { labels, chartData } = useInsightChart({ @@ -81,7 +83,7 @@ export const GpuInsightChart = ({ dataType, period, offset, - gpuName: "NVIDIA GeForce RTX 3060 Ti", + gpuName, }); const chartConfig: Record = { diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..6a2f965 --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,64 @@ +import * as TabsPrimitive from "@radix-ui/react-tabs"; +import type * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Tabs({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function TabsList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 6b67bf4..22a95de 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -1,7 +1,7 @@ { "shared": { "usage": "使用率", - "cpuUsage": "CPU使用率", + "cpuUsage": "CPU 使用率", "memoryUsage": "メモリ使用率", "temperature": "温度", "name": "名前", diff --git a/src/template/Insights.tsx b/src/template/Insights.tsx index 9ab6cd6..83f233c 100644 --- a/src/template/Insights.tsx +++ b/src/template/Insights.tsx @@ -1,4 +1,4 @@ -import { insightMenuAtom } from "@/atom/ui"; +import { useHardwareInfoAtom } from "@/atom/useHardwareInfoAtom"; import { GpuInsightChart, InsightChart, @@ -10,6 +10,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { archivePeriods } from "@/consts"; import { useTauriStore } from "@/hooks/useTauriStore"; import type { @@ -17,10 +18,8 @@ import type { DataStats, HardwareDataType, } from "@/types/hardwareDataType"; -import type { InsightChildMenuType } from "@/types/ui"; -import { useAtom } from "jotai"; import { ChevronLeft, ChevronRight } from "lucide-react"; -import { type JSX, useState } from "react"; +import { type JSX, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; @@ -241,7 +240,7 @@ const MainInsights = () => { ); }; -const GPUInsights = () => { +const GPUInsights = ({ gpuName }: { gpuName: string }) => { const { t } = useTranslation(); const [periodAvgGpuUsage, setPeriodAvgGpuUsage] = useTauriStore< (typeof archivePeriods)[number] | null @@ -387,8 +386,8 @@ const GPUInsights = () => { <>

    - {t(`shared.${dataTypeKeys}`)} ({t(`shared.${data.stats}`)} - ) + GPU {t(`shared.${dataTypeKeys}`)} ( + {t(`shared.${data.stats}`)})

    { period={periodData} dataStats={stats} offset={offset} + gpuName={gpuName} /> - {type === "insights" && displayTarget === type && ( - - )} ) ); @@ -137,25 +133,3 @@ export const SideMenu = memo(() => { ) ); }); - -const InsightChildMenu = memo(() => { - const { t } = useTranslation(); - const [displayTarget, setDisplayTarget] = useAtom(insightMenuAtom); - - return insightChildMenu.map((menuType) => ( -
  • - -
  • - )); -}); From 407d834d78b672bef35e8ed40f14e9b33fba9d94 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 16 Mar 2025 14:47:40 +0900 Subject: [PATCH 7/7] Fix: `package.lock.json` --- package-lock.json | 167 ++++++++++++++++++++++++++++++---------------- 1 file changed, 109 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index e775455..d428aef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -686,7 +686,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -703,7 +702,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -720,7 +718,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -737,7 +734,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -754,7 +750,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -771,7 +766,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -788,7 +782,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -805,7 +798,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -822,7 +814,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -839,7 +830,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -856,7 +846,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -873,7 +862,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -890,7 +878,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -907,7 +894,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -924,7 +910,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -941,7 +926,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -958,7 +942,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -975,7 +958,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -992,7 +974,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1009,7 +990,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1026,7 +1006,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1043,7 +1022,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1060,7 +1038,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1077,7 +1054,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1094,7 +1070,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2165,7 +2140,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2179,7 +2153,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2193,7 +2166,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2207,7 +2179,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2221,7 +2192,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2235,7 +2205,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2249,7 +2218,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2263,7 +2231,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2277,7 +2244,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2291,7 +2257,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2305,7 +2270,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2319,7 +2283,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2333,7 +2296,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2347,7 +2309,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2361,7 +2322,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2375,7 +2335,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2389,7 +2348,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2403,7 +2361,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2417,7 +2374,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2908,6 +2864,27 @@ "@tauri-apps/api": "^2.0.0" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@testing-library/jest-dom": { "version": "6.6.3", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", @@ -2992,6 +2969,14 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3104,14 +3089,13 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -3121,7 +3105,7 @@ "version": "19.0.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -3131,7 +3115,7 @@ "version": "19.0.4", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -3506,6 +3490,24 @@ "node": ">=12" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -3833,6 +3835,14 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -3965,7 +3975,6 @@ "version": "0.24.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -4084,7 +4093,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -4854,6 +4862,17 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -4985,7 +5004,6 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, "funding": [ { "type": "github", @@ -5098,14 +5116,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/postcss": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5130,6 +5146,36 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5228,6 +5274,14 @@ } } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -5400,7 +5454,6 @@ "version": "4.34.8", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -5525,7 +5578,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -5856,7 +5908,7 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -5870,7 +5922,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -5973,7 +6025,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz", "integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.24.2",