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/**'],