diff --git a/build.ts b/build.ts index e0c1887..717f431 100644 --- a/build.ts +++ b/build.ts @@ -16,6 +16,13 @@ import { Schema } from '@effect/schema' import { $ } from 'bun' import { Console, Context, Data, Effect, Layer } from 'effect' +// ============================================================================ +// 项目配置 +// ============================================================================ + +/** 项目名称 - 用于生成 sidecar 文件名 */ +const PROJECT_NAME = 'openbridge-token-usage-viewer' + // ============================================================================ // 领域模型和 Schema 定义 // ============================================================================ @@ -195,7 +202,7 @@ class BuildService extends Context.Tag('BuildService')< Bun.build({ entrypoints: [config.entrypoint], compile: { - outfile: `app-${targetMap[target]}`, + outfile: `${PROJECT_NAME}-${targetMap[target]}`, target: target, }, outdir: config.outputDir, @@ -227,7 +234,7 @@ class BuildService extends Context.Tag('BuildService')< Bun.build({ entrypoints: [config.entrypoint], compile: { - outfile: `app-${targetMap[target]}`, + outfile: `${PROJECT_NAME}-${targetMap[target]}`, target: target, }, outdir: config.outputDir, diff --git a/package.json b/package.json index 4fcf064..c63a8e8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "fullstack-starter", + "name": "openbridge-token-usage-viewer", "private": true, "type": "module", "packageManager": "bun@1.3.6", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 95f7a9a..3542817 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -47,17 +47,6 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" -[[package]] -name = "app-desktop" -version = "0.1.0" -dependencies = [ - "serde", - "tauri", - "tauri-build", - "tauri-plugin-shell", - "tokio", -] - [[package]] name = "atk" version = "0.18.2" @@ -2071,6 +2060,17 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openbridge-token-usage-viewer" +version = "0.1.0" +dependencies = [ + "serde", + "tauri", + "tauri-build", + "tauri-plugin-shell", + "tokio", +] + [[package]] name = "option-ext" version = "0.2.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 39e7b45..111ce97 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "app-desktop" +name = "openbridge-token-usage-viewer" version = "0.1.0" -description = "A Tauri App" +description = "OpenBridge Token Usage Viewer" authors = ["imbytecat"] edition = "2021" @@ -11,7 +11,7 @@ edition = "2021" # The `_lib` suffix may seem redundant but it is necessary # to make the lib name unique and wouldn't conflict with the bin name. # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "app_desktop_lib" +name = "openbridge_token_usage_viewer_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 97a7e83..7f8c2dc 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - app_desktop_lib::run() + openbridge_token_usage_viewer_lib::run() } diff --git a/src-tauri/src/sidecar.rs b/src-tauri/src/sidecar.rs index cac00de..48828a1 100644 --- a/src-tauri/src/sidecar.rs +++ b/src-tauri/src/sidecar.rs @@ -5,14 +5,32 @@ use tauri::Manager; use tauri_plugin_shell::process::{CommandChild, CommandEvent}; use tauri_plugin_shell::ShellExt; +// ===== 项目配置 ===== + +/// 项目名称 - 用于生成稳定端口和 sidecar 命名 +const PROJECT_NAME: &str = "openbridge-token-usage-viewer"; + +/// DJB2 Hash 算法 - 将项目名称转换为稳定端口 +fn djb2_hash(s: &str) -> u32 { + let mut hash: u32 = 5381; + for c in s.bytes() { + hash = hash.wrapping_shl(5).wrapping_add(hash).wrapping_add(c as u32); + } + hash +} + +/// 计算项目专用端口 (范围: 10000-60000) +fn get_project_port() -> u16 { + const PORT_MIN: u16 = 10000; + const PORT_RANGE: u32 = 50000; + PORT_MIN + (djb2_hash(PROJECT_NAME) % PORT_RANGE) as u16 +} + // ===== 配置常量 ===== /// Sidecar App 启动超时时间(秒) const STARTUP_TIMEOUT_SECS: u64 = 30; -/// 默认起始端口 -const DEFAULT_PORT: u16 = 3000; - /// 端口扫描范围(从起始端口开始扫描的端口数量) const PORT_SCAN_RANGE: u16 = 100; @@ -23,7 +41,7 @@ const DEFAULT_WINDOW_WIDTH: f64 = 1200.0; const DEFAULT_WINDOW_HEIGHT: f64 = 800.0; /// 窗口标题 -const WINDOW_TITLE: &str = "Tauri App"; +const WINDOW_TITLE: &str = "OpenBridge Token Usage Viewer"; // ===== 数据结构 ===== @@ -72,15 +90,20 @@ fn show_error_dialog(message: &str) { pub fn spawn_sidecar(app_handle: tauri::AppHandle) { // 检测是否为开发模式 let is_dev = cfg!(debug_assertions); + // 获取项目专用端口 + let project_port = get_project_port(); if is_dev { // 开发模式:直接创建窗口连接到 Vite 开发服务器 println!("🔧 开发模式"); + println!("📌 项目: {}", PROJECT_NAME); + println!("📌 端口: {}", project_port); + let dev_url = format!("http://localhost:{}", project_port); match tauri::WebviewWindowBuilder::new( &app_handle, "main", - tauri::WebviewUrl::External("http://localhost:3000".parse().unwrap()), + tauri::WebviewUrl::External(dev_url.parse().unwrap()), ) .title(WINDOW_TITLE) .inner_size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT) @@ -99,13 +122,14 @@ pub fn spawn_sidecar(app_handle: tauri::AppHandle) { // 生产模式:启动 sidecar 二进制 tauri::async_runtime::spawn(async move { println!("🚀 生产模式"); + println!("📌 项目: {}", PROJECT_NAME); - // 查找可用端口 - let port = find_available_port(DEFAULT_PORT).await; - println!("使用端口: {}", port); + // 查找可用端口 (从项目端口开始扫描) + let port = find_available_port(project_port).await; + println!("📌 端口: {}", port); - // 启动 sidecar - let sidecar = match app_handle.shell().sidecar("app") { + // 启动 sidecar (使用项目名称作为 sidecar 名称) + let sidecar = match app_handle.shell().sidecar(PROJECT_NAME) { Ok(cmd) => cmd.env("PORT", port.to_string()), Err(e) => { eprintln!("✗ 无法找到 sidecar: {}", e); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 8c0aa7a..1b45374 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,8 +1,8 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "app-desktop", + "productName": "openbridge-token-usage-viewer", "version": "0.1.0", - "identifier": "com.imbytecat.app-desktop", + "identifier": "com.imbytecat.openbridge-token-usage-viewer", "app": { "withGlobalTauri": true, "windows": [], @@ -20,6 +20,6 @@ "icons/icon.icns", "icons/icon.ico" ], - "externalBin": ["binaries/app"] + "externalBin": ["binaries/openbridge-token-usage-viewer"] } } diff --git a/src/components/TokenUsageDashboard.tsx b/src/components/TokenUsageDashboard.tsx index 479af77..68a8428 100644 --- a/src/components/TokenUsageDashboard.tsx +++ b/src/components/TokenUsageDashboard.tsx @@ -7,10 +7,12 @@ * 特性: * - 多账户配额可视化 (根据 API 返回的账户数量动态显示) * - 实时告警通知 (低于 20% 警告,低于 5% 紧急) - * - 支持日间/夜间主题切换 + * - 支持 OpenBridge 四种主题切换 (day/bright/dusk/night) * - OpenBridge 组件懒加载以避免 SSR 问题 */ import '@oicl/openbridge-webcomponents/dist/icons/icon-palette-day.js' +import '@oicl/openbridge-webcomponents/dist/icons/icon-palette-day-bright.js' +import '@oicl/openbridge-webcomponents/dist/icons/icon-palette-dusk.js' import '@oicl/openbridge-webcomponents/dist/icons/icon-palette-night.js' import { AlertType } from '@oicl/openbridge-webcomponents/dist/types' import { lazy, Suspense, useCallback, useMemo, useState } from 'react' @@ -291,6 +293,37 @@ export const TokenUsageDashboard = ({ data }: TokenUsageDashboardProps) => { /> + {/* 明亮模式选项 */} + handleThemeChange('bright')} + > + ', + }} + /> + + + {/* 黄昏模式选项 */} + handleThemeChange('dusk')} + > + ', + }} + /> + + {/* 夜间模式选项 */} { + let hash = 5381 + for (let i = 0; i < str.length; i++) { + // hash * 33 + char + hash = ((hash << 5) + hash + str.charCodeAt(i)) >>> 0 + } + return hash +} + +/** + * 将项目名称转换为稳定的端口号 + * + * @param projectName - 项目名称 + * @returns 10000-60000 范围内的端口号 + */ +export const getPortFromName = (projectName: string): number => { + const hash = djb2Hash(projectName) + return PORT_MIN + (hash % PORT_RANGE) +} + +/** + * 项目专用端口 + * + * 基于项目名称计算的稳定端口号。 + * 用于开发服务器和 Tauri sidecar。 + */ +export const PROJECT_PORT = getPortFromName(PROJECT_NAME) + +// 用于调试: 在构建时输出端口信息 +if (import.meta.env?.DEV) { + console.log(`📌 项目: ${PROJECT_NAME}`) + console.log(`📌 端口: ${PROJECT_PORT}`) +} diff --git a/vite.config.ts b/vite.config.ts index cb9f898..00a745f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,7 +7,7 @@ * - Bun 运行时优化 (nitro preset: 'bun') * - 静态资源内联 (serveStatic: 'inline') * - React Compiler 自动优化 (无需手动 memo) - * - 开发时热更新 (端口 3000) + * - 基于项目名称的稳定端口 (使用 DJB2 hash) */ import tailwindcss from '@tailwindcss/vite' import { devtools as tanstackDevtools } from '@tanstack/devtools-vite' @@ -17,8 +17,27 @@ import { nitro } from 'nitro/vite' import { defineConfig } from 'vite' import tsconfigPaths from 'vite-tsconfig-paths' -/** 开发服务器端口 */ -const DEV_PORT = 3000 +// ============================================================================ +// 项目配置 (集中管理) +// ============================================================================ + +/** 项目名称 - 用于生成稳定端口和 sidecar 命名 */ +export const PROJECT_NAME = 'openbridge-token-usage-viewer' + +/** + * DJB2 Hash 算法 - 将项目名称转换为稳定端口 + * 端口范围: 10000-60000 + */ +const djb2Hash = (str: string): number => { + let hash = 5381 + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) + hash + str.charCodeAt(i)) >>> 0 + } + return hash +} + +/** 开发服务器端口 (基于项目名称的稳定值) */ +export const DEV_PORT = 10000 + (djb2Hash(PROJECT_NAME) % 50000) export default defineConfig({ // 禁止清屏,方便与 Tauri 开发工具共用终端 @@ -60,7 +79,7 @@ export default defineConfig({ server: { port: DEV_PORT, // 如果端口被占用则报错,而不是自动切换端口 - strictPort: true, + strictPort: false, watch: { // 忽略 Tauri 源码目录,避免不必要的重编译 ignored: ['**/src-tauri/**'],