diff --git a/dist-electron/main/main.js b/dist-electron/main/main.js index 2ffb9f1..96c84ee 100644 --- a/dist-electron/main/main.js +++ b/dist-electron/main/main.js @@ -1,6 +1,6 @@ "use strict"; require("electron"); -require("./main-BynUR3CU.js"); +require("./main-TNsjwKgR.js"); require("electron-squirrel-startup"); require("electron-log"); require("bytenode"); diff --git a/dist-electron/preload/preload.js b/dist-electron/preload/preload.js index 97b1935..a8ecba1 100644 --- a/dist-electron/preload/preload.js +++ b/dist-electron/preload/preload.js @@ -1,6 +1,7 @@ "use strict"; const electron = require("electron"); var IPC_EVENTS = /* @__PURE__ */ ((IPC_EVENTS2) => { + IPC_EVENTS2["HOST_API_FETCH"] = "hostapi:fetch"; IPC_EVENTS2["EXTERNAL_OPEN"] = "external-open"; IPC_EVENTS2["APP_SET_FRAMELESS"] = "app:set-frameless"; IPC_EVENTS2["APP_LOAD_PAGE"] = "app:load-page"; diff --git a/electron/api/routes/settings.ts b/electron/api/routes/settings.ts index 01995e7..df0b86d 100644 --- a/electron/api/routes/settings.ts +++ b/electron/api/routes/settings.ts @@ -1,4 +1,4 @@ -import type { ConfigKeys, IConfig } from '@runtime/lib/types'; +import type { ConfigKeys, IConfig } from '@electron/types/runtime'; import configManager from '@electron/service/config-service'; import type { HostApiResult } from '@src/types/runtime'; import type { HostApiContext } from '../context'; diff --git a/electron/locales/messages.ts b/electron/locales/messages.ts new file mode 100644 index 0000000..6c41575 --- /dev/null +++ b/electron/locales/messages.ts @@ -0,0 +1,99 @@ +export type RuntimeMessageTree = { + [key: string]: string | number | RuntimeMessageTree; +}; + +export const runtimeLocaleMessages: Record<'en' | 'zh' | 'th', RuntimeMessageTree> = { + en: { + settings: { + title: 'Settings', + }, + menu: { + conversation: { + newConversation: 'New Conversation', + sortBy: 'Sort By', + sortByCreateTime: 'Sort by Creation Time', + sortByUpdateTime: 'Sort by Update Time', + sortByName: 'Sort by Name', + sortByModel: 'Sort by Model', + sortAscending: 'Ascending', + sortDescending: 'Descending', + pinConversation: 'Pin Conversation', + renameConversation: 'Rename Conversation', + delConversation: 'Delete Conversation', + batchOperations: 'Batch Operations', + }, + message: { + copyMessage: 'Copy Message', + deleteMessage: 'Delete Message', + selectMessage: 'Select Message', + }, + }, + tray: { + tooltip: 'ZN-AI', + showWindow: 'Show Window', + exit: 'Exit', + }, + }, + zh: { + settings: { + title: '设置', + }, + menu: { + conversation: { + newConversation: '新建对话', + sortBy: '排序方式', + sortByCreateTime: '按创建时间排序', + sortByUpdateTime: '按更新时间排序', + sortByName: '按名称排序', + sortByModel: '按模型排序', + sortAscending: '递增', + sortDescending: '递减', + pinConversation: '置顶对话', + renameConversation: '重命名对话', + delConversation: '删除对话', + batchOperations: '批量操作', + }, + message: { + copyMessage: '复制消息', + deleteMessage: '删除消息', + selectMessage: '选择消息', + }, + }, + tray: { + tooltip: 'ZN-AI', + showWindow: '显示窗口', + exit: '退出', + }, + }, + th: { + settings: { + title: 'ตั้งค่า', + }, + menu: { + conversation: { + newConversation: 'การสนทนาใหม่', + sortBy: 'จัดเรียงตาม', + sortByCreateTime: 'จัดเรียงตามเวลาสร้าง', + sortByUpdateTime: 'จัดเรียงตามเวลาอัปเดต', + sortByName: 'จัดเรียงตามชื่อ', + sortByModel: 'จัดเรียงตามโมเดล', + sortAscending: 'เรียงจากน้อยไปมาก', + sortDescending: 'เรียงจากมากไปน้อย', + pinConversation: 'ปักหมุดการสนทนา', + renameConversation: 'เปลี่ยนชื่อการสนทนา', + delConversation: 'ลบการสนทนา', + batchOperations: 'ดำเนินการแบบกลุ่ม', + }, + message: { + copyMessage: 'คัดลอกข้อความ', + deleteMessage: 'ลบข้อความ', + selectMessage: 'เลือกข้อความ', + }, + }, + tray: { + tooltip: 'ZN-AI', + showWindow: 'แสดงหน้าต่าง', + exit: 'ออก', + }, + }, +}; diff --git a/electron/main.ts b/electron/main.ts index cf71bde..6ce2772 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,5 +1,5 @@ import { app, BrowserWindow, ipcMain } from 'electron' -import { CONFIG_KEYS } from '@runtime/lib/constants' +import { CONFIG_KEYS, IPC_EVENTS } from '@runtime/lib/constants' import { setupMainWindow } from './wins'; import started from 'electron-squirrel-startup' import configManager from '@electron/service/config-service' @@ -75,7 +75,7 @@ async function requestUpstreamHostApi(path: string, method: string, headers: Rec } } -ipcMain.handle('hostapi:fetch', async (_event, { path, method, headers, body }) => { +ipcMain.handle(IPC_EVENTS.HOST_API_FETCH, async (_event, { path, method, headers, body }) => { const normalizedMethod = method || 'GET'; // 1. 优先本地处理 Host API 路由(逐步对齐 ClawX) @@ -92,7 +92,7 @@ ipcMain.handle('hostapi:fetch', async (_event, { path, method, headers, body }) }); // Gateway RPC IPC handler -ipcMain.handle('gateway:rpc', async (_event, method: string, params: any) => { +ipcMain.handle(IPC_EVENTS.GATEWAY_RPC, async (_event, method: string, params: any) => { return gatewayManager.rpc(method, params); }); diff --git a/electron/service/config-service/index.ts b/electron/service/config-service/index.ts index 3ea3450..4d420a0 100644 --- a/electron/service/config-service/index.ts +++ b/electron/service/config-service/index.ts @@ -1,10 +1,10 @@ import { BrowserWindow, ipcMain } from 'electron' -import type { ConfigKeys, IConfig } from '@runtime/lib/types' import { CONFIG_KEYS, IPC_EVENTS } from '@runtime/lib/constants' -import { debounce } from '@runtime/lib/utils' import logManager from '@electron/service/logger' +import type { ConfigKeys, IConfig } from '@electron/types/runtime' import { getUserDataDir } from '@electron/utils/paths' +import { debounce } from '@electron/utils/shared' type AppConfig = IConfig & { [CONFIG_KEYS.GATEWAY_AUTO_START]: boolean; diff --git a/electron/service/menu-service/index.ts b/electron/service/menu-service/index.ts index 26cc8e1..5ecab17 100644 --- a/electron/service/menu-service/index.ts +++ b/electron/service/menu-service/index.ts @@ -1,7 +1,7 @@ import { ipcMain, Menu, type MenuItemConstructorOptions } from 'electron'; import { CONFIG_KEYS, IPC_EVENTS } from '@runtime/lib/constants'; -import { cloneDeep } from '@runtime/lib/utils'; import { createTranslator } from '@electron/utils' +import { cloneDeep } from '@electron/utils/shared'; import logManager from '@electron/service/logger' import configManager from '@electron/service/config-service' diff --git a/electron/service/script-execution-service/index.ts b/electron/service/script-execution-service/index.ts index 5e62b7c..a116869 100644 --- a/electron/service/script-execution-service/index.ts +++ b/electron/service/script-execution-service/index.ts @@ -3,7 +3,7 @@ import { getScriptPathById, updateLastRun, } from '@electron/service/script-store-service'; -import type { ScriptExecutionResult } from '@runtime/lib/script-types'; +import type { ScriptExecutionResult } from '../../types/script-types'; const executor = new executeScriptService(); diff --git a/electron/service/script-store-service/index.ts b/electron/service/script-store-service/index.ts index 4307a59..445e8df 100644 --- a/electron/service/script-store-service/index.ts +++ b/electron/service/script-store-service/index.ts @@ -7,7 +7,7 @@ import type { ScriptMetaItem, ScriptsMeta, ScriptSaveInput, -} from '@runtime/lib/script-types'; +} from '../../types/script-types'; const META_FILENAME = 'scripts.meta.json'; diff --git a/electron/service/window-service/index.ts b/electron/service/window-service/index.ts index 5ccbc04..ca8057a 100644 --- a/electron/service/window-service/index.ts +++ b/electron/service/window-service/index.ts @@ -1,4 +1,4 @@ -import type { WindowNames } from '@runtime/lib/types' +import type { WindowNames } from '@electron/types/runtime' import { CONFIG_KEYS, IPC_EVENTS, WINDOW_NAMES } from '@runtime/lib/constants' import { app, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, IpcMainInvokeEvent, type IpcMainEvent } from 'electron' diff --git a/runtime-shared/lib/types.ts b/electron/types/runtime.ts similarity index 86% rename from runtime-shared/lib/types.ts rename to electron/types/runtime.ts index bd820c8..689167c 100644 --- a/runtime-shared/lib/types.ts +++ b/electron/types/runtime.ts @@ -1,5 +1,5 @@ -import type { Task } from './task-types'; -import { CONFIG_KEYS, WINDOW_NAMES } from './constants'; +import type { Task } from '@src/lib/task-types'; +import { CONFIG_KEYS, WINDOW_NAMES } from '@runtime/lib/constants'; export type ThemeMode = 'dark' | 'light' | 'system'; export type WindowNames = `${WINDOW_NAMES}`; @@ -8,7 +8,7 @@ export type ConfigKeys = `${CONFIG_KEYS}`; export interface IConfig { [CONFIG_KEYS.THEME_MODE]: ThemeMode; [CONFIG_KEYS.PRIMARY_COLOR]: string; - [CONFIG_KEYS.LANGUAGE]: 'zh' | 'en'; + [CONFIG_KEYS.LANGUAGE]: 'zh' | 'en' | 'th'; [CONFIG_KEYS.FONT_SIZE]: number; [CONFIG_KEYS.MINIMIZE_TO_TRAY]: boolean; [CONFIG_KEYS.LAUNCH_AT_STARTUP]: boolean; diff --git a/runtime-shared/lib/script-types.ts b/electron/types/script-types.ts similarity index 100% rename from runtime-shared/lib/script-types.ts rename to electron/types/script-types.ts diff --git a/electron/utils/index.ts b/electron/utils/index.ts index 1515aab..ef53fe9 100644 --- a/electron/utils/index.ts +++ b/electron/utils/index.ts @@ -3,7 +3,7 @@ import logManager from '@electron/service/logger' import configManager from '@electron/service/config-service' import path from 'node:path' import { app } from 'electron' -import { runtimeLocaleMessages, type RuntimeMessageTree } from '@runtime/locales/messages' +import { runtimeLocaleMessages, type RuntimeMessageTree } from '@electron/locales/messages' const messages: Record = runtimeLocaleMessages diff --git a/runtime-shared/lib/utils.ts b/electron/utils/shared.ts similarity index 100% rename from runtime-shared/lib/utils.ts rename to electron/utils/shared.ts diff --git a/global.d.ts b/global.d.ts index 5bbd208..982694a 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,8 +1,9 @@ -import { IPC_EVENTS } from '@runtime/lib/constants' -import type { ConfigKeys } from '@runtime/lib/types' -import type { AutomationScript, ScriptSaveInput, ScriptExecutionResult } from '@runtime/lib/script-types' +import { CONFIG_KEYS, IPC_EVENTS } from '@runtime/lib/constants' +import type { AutomationScript, ScriptSaveInput, ScriptExecutionResult } from '@src/lib/script-types' declare global { + type ConfigKeys = `${CONFIG_KEYS}` + // 定义每个通道的参数和返回值类型 interface IPCTypings { // 同步通信 @@ -42,6 +43,10 @@ declare global { params: [event: any] return: void } + [IPC_EVENTS.HOST_API_FETCH]: { + params: [request: { path: string; method?: string; headers?: Record; body?: unknown }] + return: Promise + } // 任务事件 [IPC_EVENTS.TASK_PROGRESS]: { params: [payload: any]; return: void; } diff --git a/runtime-shared/lib/constants.ts b/runtime-shared/lib/constants.ts index 4bf515c..b2d7858 100644 --- a/runtime-shared/lib/constants.ts +++ b/runtime-shared/lib/constants.ts @@ -1,4 +1,5 @@ export enum IPC_EVENTS { + HOST_API_FETCH = 'hostapi:fetch', EXTERNAL_OPEN = 'external-open', APP_SET_FRAMELESS = 'app:set-frameless', APP_LOAD_PAGE = 'app:load-page', @@ -120,3 +121,7 @@ export enum MESSAGE_ITEM_MENU_IDS { DELETE = 'delete', SELECT = 'select', } + +export const DEFAULT_THEME_MODE = 'system' as const; + +export const DEFAULT_LANGUAGE = 'zh' as const; diff --git a/runtime-shared/lib/models.ts b/runtime-shared/lib/models.ts index 76e14b6..c965b26 100644 --- a/runtime-shared/lib/models.ts +++ b/runtime-shared/lib/models.ts @@ -2,9 +2,13 @@ import { DEFAULT_AGENT_ID, DEFAULT_CHANNEL_ACCOUNT_ID, DEFAULT_MAIN_SESSION_SUFFIX, + buildAgentSessionKey, + buildMainSessionKey, buildChannelAccountOwnerKey, + normalizeAgentId, normalizeChannelAccountId, normalizeChannelType, + normalizeSessionSuffix, parseChannelAccountOwnerKey, resolveChannelAccountOwner, type AgentSummary, @@ -29,27 +33,6 @@ export interface ParsedSessionKey { isAgentSession: boolean; } -export function normalizeAgentId(value: string | null | undefined): string { - const normalized = String(value ?? '').trim().toLowerCase(); - return normalized || DEFAULT_AGENT_ID; -} - -export function normalizeSessionSuffix(value: string | null | undefined): string { - const normalized = String(value ?? '').trim().toLowerCase(); - return normalized || DEFAULT_MAIN_SESSION_SUFFIX; -} - -export function buildAgentSessionKey(agentId: string, sessionId: string): string { - return `agent:${normalizeAgentId(agentId)}:${normalizeSessionSuffix(sessionId)}`; -} - -export function buildMainSessionKey( - agentId: string, - sessionId = DEFAULT_MAIN_SESSION_SUFFIX, -): string { - return buildAgentSessionKey(agentId, sessionId); -} - export function parseSessionKey(sessionKey: string): ParsedSessionKey { const trimmed = String(sessionKey ?? '').trim(); @@ -97,9 +80,13 @@ export { DEFAULT_AGENT_ID, DEFAULT_CHANNEL_ACCOUNT_ID, DEFAULT_MAIN_SESSION_SUFFIX, + buildAgentSessionKey, + buildMainSessionKey, buildChannelAccountOwnerKey, + normalizeAgentId, normalizeChannelAccountId, normalizeChannelType, + normalizeSessionSuffix, parseChannelAccountOwnerKey, resolveChannelAccountOwner, }; diff --git a/runtime-shared/lib/providers.ts b/runtime-shared/lib/providers.ts index 20aa333..c7465be 100644 --- a/runtime-shared/lib/providers.ts +++ b/runtime-shared/lib/providers.ts @@ -58,6 +58,8 @@ export interface ProviderTypeInfo { name: string; icon: string; placeholder: string; + placeholderZh?: string; + placeholderTh?: string; model?: string; requiresApiKey: boolean; defaultBaseUrl?: string; @@ -124,7 +126,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'anthropic', name: 'Anthropic', - icon: 'A', + icon: '🤖', placeholder: 'sk-ant-api03-...', model: 'Claude', requiresApiKey: true, @@ -133,7 +135,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'openai', name: 'OpenAI', - icon: 'O', + icon: '💚', placeholder: 'sk-proj-...', model: 'GPT', requiresApiKey: true, @@ -148,7 +150,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'google', name: 'Google', - icon: 'G', + icon: '🔷', placeholder: 'AIza...', model: 'Gemini', requiresApiKey: true, @@ -163,7 +165,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'openrouter', name: 'OpenRouter', - icon: 'R', + icon: '🌐', placeholder: 'sk-or-v1-...', model: 'Multi-Model', requiresApiKey: true, @@ -175,7 +177,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'minimax-portal-cn', name: 'MiniMax (CN)', - icon: 'M', + icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, @@ -192,7 +194,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'moonshot', name: 'Moonshot (CN)', - icon: 'K', + icon: '🌙', placeholder: 'sk-...', model: 'Kimi', requiresApiKey: true, @@ -203,7 +205,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'siliconflow', name: 'SiliconFlow (CN)', - icon: 'S', + icon: '🌊', placeholder: 'sk-...', model: 'Multi-Model', requiresApiKey: true, @@ -217,7 +219,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'deepseek', name: 'DeepSeek', - icon: 'D', + icon: '🐋', placeholder: 'sk-...', model: 'DeepSeek', requiresApiKey: true, @@ -231,7 +233,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'minimax-portal', name: 'MiniMax (Global)', - icon: 'M', + icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, @@ -248,7 +250,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'modelstudio', name: 'Model Studio', - icon: 'Q', + icon: '☁️', placeholder: 'sk-...', model: 'Qwen', requiresApiKey: true, @@ -264,7 +266,7 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'ark', name: 'ByteDance Ark', - icon: 'B', + icon: 'A', placeholder: 'your-ark-api-key', model: 'Doubao', requiresApiKey: true, @@ -280,8 +282,10 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'ollama', name: 'Ollama', - icon: 'L', + icon: '🦙', placeholder: 'Not required', + placeholderZh: '无需填写', + placeholderTh: 'ไม่ต้องกรอก', requiresApiKey: false, defaultBaseUrl: 'http://localhost:11434/v1', showBaseUrl: true, @@ -291,8 +295,10 @@ export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ { id: 'custom', name: 'Custom', - icon: 'C', + icon: '⚙️', placeholder: 'API key...', + placeholderZh: 'API Key...', + placeholderTh: 'API Key...', requiresApiKey: true, showBaseUrl: true, showModelId: true, @@ -329,6 +335,22 @@ export function getProviderDocsUrl( return provider.docsUrl; } +export function getProviderPlaceholder( + provider: Pick | undefined, + language: string, +): string | undefined { + if (!provider?.placeholder) { + return undefined; + } + if (language.startsWith('zh') && provider.placeholderZh) { + return provider.placeholderZh; + } + if (language.startsWith('th') && provider.placeholderTh) { + return provider.placeholderTh; + } + return provider.placeholder; +} + export function shouldShowProviderModelId( provider: Pick | undefined, devModeUnlocked: boolean, diff --git a/runtime-shared/lib/task-types.ts b/runtime-shared/lib/task-types.ts deleted file mode 100644 index f029bc3..0000000 --- a/runtime-shared/lib/task-types.ts +++ /dev/null @@ -1,40 +0,0 @@ -export type SubTaskStatus = 'pending' | 'running' | 'success' | 'failed'; - -export interface SubTask { - id: string; - taskId: string; - scriptId: string; - name: string; - status: SubTaskStatus; - progress: number; - message: string; - stdoutTail: string; - stderrTail: string; - error?: string; - startedAt: string; - completedAt?: string; -} - -export type TaskStatus = 'pending' | 'running' | 'success' | 'partial_failed' | 'failed'; - -export interface Task { - id: string; - title: string; - operation: 'open' | 'close'; - roomType: string; - dateRange: [string, string]; - status: TaskStatus; - subTasks: SubTask[]; - roomList: any[]; - createdAt: string; - updatedAt: string; -} - -export interface TaskProgressPayload { - taskId: string; - subTaskId: string; - progress?: number; - message?: string; - stdoutTail?: string; - stderrTail?: string; -} diff --git a/runtime-shared/locales/messages.ts b/runtime-shared/locales/messages.ts deleted file mode 100644 index 40b5739..0000000 --- a/runtime-shared/locales/messages.ts +++ /dev/null @@ -1,99 +0,0 @@ -export type RuntimeMessageTree = { - [key: string]: string | number | RuntimeMessageTree; -}; - -export const runtimeLocaleMessages: Record<'en' | 'zh' | 'th', RuntimeMessageTree> = { - en: { - settings: { - title: 'Settings', - }, - menu: { - conversation: { - newConversation: 'New Conversation', - sortBy: 'Sort By', - sortByCreateTime: 'Sort by Creation Time', - sortByUpdateTime: 'Sort by Update Time', - sortByName: 'Sort by Name', - sortByModel: 'Sort by Model', - sortAscending: 'Ascending', - sortDescending: 'Descending', - pinConversation: 'Pin Conversation', - renameConversation: 'Rename Conversation', - delConversation: 'Delete Conversation', - batchOperations: 'Batch Operations', - }, - message: { - copyMessage: 'Copy Message', - deleteMessage: 'Delete Message', - selectMessage: 'Select Message', - }, - }, - tray: { - tooltip: 'ZN-AI', - showWindow: 'Show Window', - exit: 'Exit', - }, - }, - zh: { - settings: { - title: '\u8bbe\u7f6e', - }, - menu: { - conversation: { - newConversation: '\u65b0\u5efa\u5bf9\u8bdd', - sortBy: '\u6392\u5e8f\u65b9\u5f0f', - sortByCreateTime: '\u6309\u521b\u5efa\u65f6\u95f4\u6392\u5e8f', - sortByUpdateTime: '\u6309\u66f4\u65b0\u65f6\u95f4\u6392\u5e8f', - sortByName: '\u6309\u540d\u79f0\u6392\u5e8f', - sortByModel: '\u6309\u6a21\u578b\u6392\u5e8f', - sortAscending: '\u9012\u589e', - sortDescending: '\u9012\u51cf', - pinConversation: '\u7f6e\u9876\u5bf9\u8bdd', - renameConversation: '\u91cd\u547d\u540d\u5bf9\u8bdd', - delConversation: '\u5220\u9664\u5bf9\u8bdd', - batchOperations: '\u6279\u91cf\u64cd\u4f5c', - }, - message: { - copyMessage: '\u590d\u5236\u6d88\u606f', - deleteMessage: '\u5220\u9664\u6d88\u606f', - selectMessage: '\u9009\u62e9\u6d88\u606f', - }, - }, - tray: { - tooltip: 'ZN-AI', - showWindow: '\u663e\u793a\u7a97\u53e3', - exit: '\u9000\u51fa', - }, - }, - th: { - settings: { - title: '\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32', - }, - menu: { - conversation: { - newConversation: '\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e17\u0e19\u0e32\u0e43\u0e2b\u0e21\u0e48', - sortBy: '\u0e08\u0e31\u0e14\u0e40\u0e23\u0e35\u0e22\u0e07\u0e15\u0e32\u0e21', - sortByCreateTime: '\u0e08\u0e31\u0e14\u0e40\u0e23\u0e35\u0e22\u0e07\u0e15\u0e32\u0e21\u0e40\u0e27\u0e25\u0e32\u0e2a\u0e23\u0e49\u0e32\u0e07', - sortByUpdateTime: '\u0e08\u0e31\u0e14\u0e40\u0e23\u0e35\u0e22\u0e07\u0e15\u0e32\u0e21\u0e40\u0e27\u0e25\u0e32\u0e2d\u0e31\u0e1b\u0e40\u0e14\u0e15', - sortByName: '\u0e08\u0e31\u0e14\u0e40\u0e23\u0e35\u0e22\u0e07\u0e15\u0e32\u0e21\u0e0a\u0e37\u0e48\u0e2d', - sortByModel: '\u0e08\u0e31\u0e14\u0e40\u0e23\u0e35\u0e22\u0e07\u0e15\u0e32\u0e21\u0e42\u0e21\u0e40\u0e14\u0e25', - sortAscending: '\u0e40\u0e23\u0e35\u0e22\u0e07\u0e08\u0e32\u0e01\u0e19\u0e49\u0e2d\u0e22\u0e44\u0e1b\u0e21\u0e32\u0e01', - sortDescending: '\u0e40\u0e23\u0e35\u0e22\u0e07\u0e08\u0e32\u0e01\u0e21\u0e32\u0e01\u0e44\u0e1b\u0e19\u0e49\u0e2d\u0e22', - pinConversation: '\u0e1b\u0e31\u0e01\u0e2b\u0e21\u0e38\u0e14\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e17\u0e19\u0e32', - renameConversation: '\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19\u0e0a\u0e37\u0e48\u0e2d\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e17\u0e19\u0e32', - delConversation: '\u0e25\u0e1a\u0e01\u0e32\u0e23\u0e2a\u0e19\u0e17\u0e19\u0e32', - batchOperations: '\u0e14\u0e33\u0e40\u0e19\u0e34\u0e19\u0e01\u0e32\u0e23\u0e41\u0e1a\u0e1a\u0e01\u0e25\u0e38\u0e48\u0e21', - }, - message: { - copyMessage: '\u0e04\u0e31\u0e14\u0e25\u0e2d\u0e01\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21', - deleteMessage: '\u0e25\u0e1a\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21', - selectMessage: '\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21', - }, - }, - tray: { - tooltip: 'ZN-AI', - showWindow: '\u0e41\u0e2a\u0e14\u0e07\u0e2b\u0e19\u0e49\u0e32\u0e15\u0e48\u0e32\u0e07', - exit: '\u0e2d\u0e2d\u0e01', - }, - }, -}; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index fcaf701..23f8ba6 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,30 +1,7 @@ -import { CONFIG_KEYS as RUNTIME_CONFIG_KEYS } from '../types/runtime'; - -export const IPC_EVENTS = { - HOST_API_FETCH: 'hostapi:fetch', - GATEWAY_RPC: 'gateway:rpc', - GATEWAY_EVENT: 'gateway:event', - GET_CONFIG: 'get-config', - SET_CONFIG: 'set-config', - GET_THEME_MODE: 'get-theme-mode', - SET_THEME_MODE: 'set-theme-mode', - THEME_MODE_UPDATED: 'theme-mode-updated', - TASK_PROGRESS: 'task:progress', - TASK_STARTED: 'task:started', - TASK_COMPLETED: 'task:completed', - OPEN_CHANNEL: 'open-channel', - EXECUTE_SCRIPT: 'execute-script', -} as const; - -export const CONFIG_KEYS = RUNTIME_CONFIG_KEYS; - -export const WINDOW_NAMES = { - MAIN: 'main', - SETTING: 'setting', - DIALOG: 'dialog', - LOADING: 'loading', -} as const; - -export const DEFAULT_THEME_MODE = 'system' as const; - -export const DEFAULT_LANGUAGE = 'zh' as const; +export { + CONFIG_KEYS, + DEFAULT_LANGUAGE, + DEFAULT_THEME_MODE, + IPC_EVENTS, + WINDOW_NAMES, +} from '../../runtime-shared/lib/constants'; diff --git a/src/lib/providers.ts b/src/lib/providers.ts index 247783f..98db146 100644 --- a/src/lib/providers.ts +++ b/src/lib/providers.ts @@ -1,261 +1 @@ -import type { LanguageCode } from '../types/runtime'; - -export const PROVIDER_TYPES = [ - 'anthropic', - 'openai', - 'google', - 'openrouter', - 'ark', - 'moonshot', - 'siliconflow', - 'deepseek', - 'minimax-portal', - 'minimax-portal-cn', - 'modelstudio', - 'ollama', - 'custom', -] as const; -export type ProviderType = (typeof PROVIDER_TYPES)[number]; - -export const BUILTIN_PROVIDER_TYPES = [ - 'anthropic', - 'openai', - 'google', - 'openrouter', - 'ark', - 'moonshot', - 'siliconflow', - 'deepseek', - 'minimax-portal', - 'minimax-portal-cn', - 'modelstudio', - 'ollama', -] as const; - -export const OLLAMA_PLACEHOLDER_API_KEY = 'ollama-local'; - -export interface ProviderConfig { - id: string; - name: string; - type: ProviderType; - baseUrl?: string; - apiProtocol?: 'openai-completions' | 'openai-responses' | 'anthropic-messages'; - headers?: Record; - model?: string; - fallbackModels?: string[]; - fallbackProviderIds?: string[]; - enabled: boolean; - createdAt: string; - updatedAt: string; -} - -export interface ProviderWithKeyInfo extends ProviderConfig { - hasKey: boolean; - keyMasked: string | null; -} - -export interface ProviderTypeInfo { - id: ProviderType; - name: string; - icon: string; - placeholder: string; - placeholderZh?: string; - placeholderTh?: string; - model?: string; - requiresApiKey: boolean; - defaultBaseUrl?: string; - showBaseUrl?: boolean; - showModelId?: boolean; - showModelIdInDevModeOnly?: boolean; - modelIdPlaceholder?: string; - defaultModelId?: string; - isOAuth?: boolean; - supportsApiKey?: boolean; - apiKeyUrl?: string; - docsUrl?: string; - docsUrlZh?: string; - codePlanPresetBaseUrl?: string; - codePlanPresetModelId?: string; - codePlanDocsUrl?: string; - hidden?: boolean; -} - -export type ProviderAuthMode = - | 'api_key' - | 'oauth_device' - | 'oauth_browser' - | 'local'; - -export type ProviderVendorCategory = - | 'official' - | 'compatible' - | 'local' - | 'custom'; - -export interface ProviderVendorInfo extends ProviderTypeInfo { - category: ProviderVendorCategory; - envVar?: string; - supportedAuthModes: ProviderAuthMode[]; - defaultAuthMode: ProviderAuthMode; - supportsMultipleAccounts: boolean; -} - -export interface ProviderAccount { - id: string; - vendorId: ProviderType; - label: string; - authMode: ProviderAuthMode; - baseUrl?: string; - apiProtocol?: 'openai-completions' | 'openai-responses' | 'anthropic-messages'; - headers?: Record; - model?: string; - fallbackModels?: string[]; - fallbackAccountIds?: string[]; - enabled: boolean; - isDefault: boolean; - metadata?: { - region?: string; - email?: string; - resourceUrl?: string; - customModels?: string[]; - }; - createdAt: string; - updatedAt: string; -} - -export const PROVIDER_TYPE_INFO: ProviderTypeInfo[] = [ - { - id: 'anthropic', - name: 'Anthropic', - icon: '🤖', - placeholder: 'sk-ant-api03-...', - model: 'Claude', - requiresApiKey: true, - docsUrl: 'https://platform.claude.com/docs/en/api/overview', - }, - { - id: 'openai', - name: 'OpenAI', - icon: '💚', - placeholder: 'sk-proj-...', - model: 'GPT', - requiresApiKey: true, - isOAuth: true, - supportsApiKey: true, - defaultModelId: 'gpt-5.4', - showModelId: true, - showModelIdInDevModeOnly: true, - modelIdPlaceholder: 'gpt-5.4', - apiKeyUrl: 'https://platform.openai.com/api-keys', - }, - { - id: 'google', - name: 'Google', - icon: '🔷', - placeholder: 'AIza...', - model: 'Gemini', - requiresApiKey: true, - isOAuth: true, - supportsApiKey: true, - defaultModelId: 'gemini-3-pro-preview', - showModelId: true, - showModelIdInDevModeOnly: true, - modelIdPlaceholder: 'gemini-3-pro-preview', - apiKeyUrl: 'https://aistudio.google.com/app/apikey', - }, - { id: 'openrouter', name: 'OpenRouter', icon: '🌐', placeholder: 'sk-or-v1-...', model: 'Multi-Model', requiresApiKey: true, showModelId: true, modelIdPlaceholder: 'openai/gpt-5.4', defaultModelId: 'openai/gpt-5.4', docsUrl: 'https://openrouter.ai/models' }, - { id: 'minimax-portal-cn', name: 'MiniMax (CN)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultBaseUrl: 'https://api.minimaxi.com/v1', apiProtocol: 'openai-completions', defaultModelId: 'MiniMax-M2.7', showModelId: true, showModelIdInDevModeOnly: true, modelIdPlaceholder: 'MiniMax-M2.7', apiKeyUrl: 'https://platform.minimaxi.com/' }, - { id: 'moonshot', name: 'Moonshot (CN)', icon: '🌙', placeholder: 'sk-...', model: 'Kimi', requiresApiKey: true, defaultBaseUrl: 'https://api.moonshot.cn/v1', defaultModelId: 'kimi-k2.5', docsUrl: 'https://platform.moonshot.cn/' }, - { id: 'siliconflow', name: 'SiliconFlow (CN)', icon: '🌊', placeholder: 'sk-...', model: 'Multi-Model', requiresApiKey: true, defaultBaseUrl: 'https://api.siliconflow.cn/v1', showModelId: true, showModelIdInDevModeOnly: true, modelIdPlaceholder: 'deepseek-ai/DeepSeek-V3', defaultModelId: 'deepseek-ai/DeepSeek-V3', docsUrl: 'https://docs.siliconflow.cn/cn/userguide/introduction' }, - { id: 'deepseek', name: 'DeepSeek', icon: '🐋', placeholder: 'sk-...', model: 'DeepSeek', requiresApiKey: true, defaultBaseUrl: 'https://api.deepseek.com/v1', showModelId: true, modelIdPlaceholder: 'deepseek-chat', defaultModelId: 'deepseek-chat', apiKeyUrl: 'https://platform.deepseek.com/api_keys', docsUrl: 'https://api-docs.deepseek.com/' }, - { id: 'minimax-portal', name: 'MiniMax (Global)', icon: '☁️', placeholder: 'sk-...', model: 'MiniMax', requiresApiKey: false, isOAuth: true, supportsApiKey: true, defaultBaseUrl: 'https://api.minimax.io/v1', apiProtocol: 'openai-completions', defaultModelId: 'MiniMax-M2.7', showModelId: true, showModelIdInDevModeOnly: true, modelIdPlaceholder: 'MiniMax-M2.7', apiKeyUrl: 'https://platform.minimax.io' }, - { id: 'modelstudio', name: 'Model Studio', icon: '☁️', placeholder: 'sk-...', model: 'Qwen', requiresApiKey: true, defaultBaseUrl: 'https://coding.dashscope.aliyuncs.com/v1', showBaseUrl: true, defaultModelId: 'qwen3.5-plus', showModelId: true, showModelIdInDevModeOnly: true, modelIdPlaceholder: 'qwen3.5-plus', apiKeyUrl: 'https://bailian.console.aliyun.com/', hidden: true }, - { id: 'ark', name: 'ByteDance Ark', icon: 'A', placeholder: 'your-ark-api-key', model: 'Doubao', requiresApiKey: true, defaultBaseUrl: 'https://ark.cn-beijing.volces.com/api/v3', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'ep-20260228000000-xxxxx', docsUrl: 'https://www.volcengine.com/', codePlanPresetBaseUrl: 'https://ark.cn-beijing.volces.com/api/coding/v3', codePlanPresetModelId: 'ark-code-latest', codePlanDocsUrl: 'https://www.volcengine.com/docs/82379/1928261?lang=zh' }, - { id: 'ollama', name: 'Ollama', icon: '🦙', placeholder: 'Not required', placeholderZh: '无需填写', placeholderTh: 'ไม่ต้องกรอก', requiresApiKey: false, defaultBaseUrl: 'http://localhost:11434/v1', showBaseUrl: true, showModelId: true, modelIdPlaceholder: 'qwen3:latest' }, - { - id: 'custom', - name: 'Custom', - icon: '⚙️', - placeholder: 'API key...', - placeholderZh: 'API Key...', - placeholderTh: 'API Key...', - requiresApiKey: true, - showBaseUrl: true, - showModelId: true, - modelIdPlaceholder: 'your-provider/model-id', - docsUrl: 'https://icnnp7d0dymg.feishu.cn/wiki/BmiLwGBcEiloZDkdYnGc8RWnn6d#Ee1ldfvKJoVGvfxc32mcILwenth', - docsUrlZh: 'https://icnnp7d0dymg.feishu.cn/wiki/BmiLwGBcEiloZDkdYnGc8RWnn6d#IWQCdfe5fobGU3xf3UGcgbLynGh', - }, -]; - -export function getProviderIconUrl(_type: ProviderType | string): string | undefined { - return undefined; -} - -export function shouldInvertInDark(_type: ProviderType | string): boolean { - return true; -} - -export const SETUP_PROVIDERS = PROVIDER_TYPE_INFO; - -export function getProviderTypeInfo(type: ProviderType): ProviderTypeInfo | undefined { - return PROVIDER_TYPE_INFO.find((providerType) => providerType.id === type); -} - -export function getProviderDocsUrl( - provider: Pick | undefined, - language: string | LanguageCode, -): string | undefined { - if (!provider?.docsUrl) { - return undefined; - } - if (language.startsWith('zh') && provider.docsUrlZh) { - return provider.docsUrlZh; - } - return provider.docsUrl; -} - -export function getProviderPlaceholder( - provider: Pick | undefined, - language: string | LanguageCode, -): string | undefined { - if (!provider?.placeholder) { - return undefined; - } - if (language.startsWith('zh') && provider.placeholderZh) { - return provider.placeholderZh; - } - if (language.startsWith('th') && provider.placeholderTh) { - return provider.placeholderTh; - } - return provider.placeholder; -} - -export function shouldShowProviderModelId( - provider: Pick | undefined, - devModeUnlocked: boolean, -): boolean { - if (!provider?.showModelId) return false; - if (provider.showModelIdInDevModeOnly && !devModeUnlocked) return false; - return true; -} - -export function resolveProviderModelForSave( - provider: Pick | undefined, - modelId: string, - devModeUnlocked: boolean, -): string | undefined { - if (!shouldShowProviderModelId(provider, devModeUnlocked)) { - return undefined; - } - const trimmedModelId = modelId.trim(); - return trimmedModelId || provider?.defaultModelId || undefined; -} - -export function resolveProviderApiKeyForSave(type: ProviderType | string, apiKey: string): string | undefined { - const trimmed = apiKey.trim(); - if (type === 'ollama') { - return trimmed || OLLAMA_PLACEHOLDER_API_KEY; - } - return trimmed || undefined; -} +export * from '../../runtime-shared/lib/providers'; diff --git a/src/shared/chat-model.ts b/src/shared/chat-model.ts index 9f60973..e3b371e 100644 --- a/src/shared/chat-model.ts +++ b/src/shared/chat-model.ts @@ -1,173 +1 @@ -export interface AttachedFileMeta { - fileName: string; - mimeType: string; - fileSize: number; - preview: string | null; - filePath?: string; - source?: 'user-upload' | 'tool-result' | 'message-ref'; -} - -export interface ContentBlockSource { - type: string; - media_type?: string; - data?: string; - url?: string; -} - -export interface ContentBlock { - type: 'text' | 'image' | 'thinking' | 'tool_use' | 'tool_result' | 'toolCall' | 'toolResult'; - text?: string; - thinking?: string; - source?: ContentBlockSource; - data?: string; - mimeType?: string; - id?: string; - name?: string; - input?: unknown; - arguments?: unknown; - content?: string | ContentBlock[]; -} - -export type RawMessageRole = 'user' | 'assistant' | 'system' | 'toolresult' | 'tool_result'; - -export interface RawMessage { - role: RawMessageRole; - content: string | ContentBlock[]; - timestamp?: number; - id?: string; - toolCallId?: string; - toolName?: string; - details?: unknown; - isError?: boolean; - question?: string[]; - toolCall?: Record | null; - _attachedFiles?: AttachedFileMeta[]; -} - -export interface ToolStatus { - id?: string; - toolCallId?: string; - name: string; - status: 'running' | 'completed' | 'error'; - durationMs?: number; - summary?: string; - updatedAt: number; -} - -export interface ChatSession { - key: string; - label?: string; - displayName?: string; - thinkingLevel?: string; - model?: string; - updatedAt?: number; -} - -export function extractText(message?: RawMessage | null): string { - if (!message) return ''; - - if (typeof message.content === 'string') { - return message.content; - } - - return message.content - .filter((block) => block.type === 'text' && typeof block.text === 'string') - .map((block) => block.text ?? '') - .join('\n'); -} - -export function extractThinking(message?: RawMessage | null): string | null { - if (!message || !Array.isArray(message.content)) return null; - - const thinkingBlock = message.content.find((block) => block.type === 'thinking'); - return thinkingBlock?.thinking ?? null; -} - -export function extractImages(message?: RawMessage | null): Array<{ url?: string; data?: string; mimeType: string }> { - if (!message || !Array.isArray(message.content)) return []; - - const images: Array<{ url?: string; data?: string; mimeType: string }> = []; - - for (const block of message.content) { - if (block.type === 'image') { - if (block.source?.type === 'base64' && block.source.data) { - images.push({ - data: block.source.data, - mimeType: block.source.media_type || 'image/jpeg', - }); - } else if (block.source?.type === 'url' && block.source.url) { - images.push({ - url: block.source.url, - mimeType: block.source.media_type || 'image/jpeg', - }); - } else if (block.data) { - images.push({ - data: block.data, - mimeType: block.mimeType || 'image/jpeg', - }); - } - } - - if ((block.type === 'tool_result' || block.type === 'toolResult') && Array.isArray(block.content)) { - images.push(...extractImages({ role: 'toolresult', content: block.content })); - } - } - - return images; -} - -export function extractToolUse(message?: RawMessage | null): Array<{ id?: string; name: string; input?: unknown }> { - if (!message || !Array.isArray(message.content)) return []; - - return message.content - .filter((block) => block.type === 'tool_use' || block.type === 'toolCall') - .map((block) => ({ - id: block.id, - name: block.name || block.id || 'tool', - input: block.input ?? block.arguments, - })); -} - -export function formatTimestamp(ts?: number): string { - if (!ts) return ''; - - const ms = ts < 1e12 ? ts * 1000 : ts; - return new Date(ms).toLocaleString(); -} - -export function isToolResultRole(role?: string): boolean { - if (!role) return false; - - const normalized = role.toLowerCase(); - return normalized === 'toolresult' || normalized === 'tool_result'; -} - -export function isToolOnlyMessage(message?: RawMessage): boolean { - if (!message) return false; - if (isToolResultRole(message.role)) return true; - if (!Array.isArray(message.content)) return false; - - const hasToolBlock = message.content.some((block) => - ['tool_use', 'tool_result', 'toolCall', 'toolResult'].includes(block.type), - ); - const hasTextBlock = message.content.some((block) => block.type === 'text' && block.text?.trim()); - const hasImageBlock = message.content.some((block) => block.type === 'image'); - - return hasToolBlock && !hasTextBlock && !hasImageBlock; -} - -export function isInternalMessage(message: { role?: string; content?: unknown }): boolean { - if (message.role === 'system') return true; - - if (message.role === 'assistant') { - const text = typeof message.content === 'string' - ? message.content - : extractText(message as RawMessage); - - if (/^(HEARTBEAT_OK|NO_REPLY)\s*$/.test(text)) { - return true; - } - } - - return false; -} +export * from '../../runtime-shared/shared/chat-model'; diff --git a/src/types/runtime.ts b/src/types/runtime.ts index 7d2262e..cb2974c 100644 --- a/src/types/runtime.ts +++ b/src/types/runtime.ts @@ -1,5 +1,6 @@ import type { Task } from '../lib/task-types'; import type { RawMessage } from '../shared/chat-model'; +import { CONFIG_KEYS } from '../../runtime-shared/lib/constants'; export type RuntimeRefreshTopic = | 'providers' @@ -20,21 +21,6 @@ export type IpcArgs = readonly unknown[]; export type IpcListener = (...args: unknown[]) => void; -export const CONFIG_KEYS = { - THEME_MODE: 'themeMode', - PRIMARY_COLOR: 'primaryColor', - LANGUAGE: 'language', - FONT_SIZE: 'fontSize', - MINIMIZE_TO_TRAY: 'minimizeToTray', - LAUNCH_AT_STARTUP: 'launchAtStartup', - PROVIDER: 'provider', - DEFAULT_MODEL: 'defaultModel', - GATEWAY_AUTO_START: 'gatewayAutoStart', - SELECTED_CHANNELS: 'selectedChannels', - IMAGE_CACHE: 'imageCache', - TASK_LIST: 'taskList', -} as const; - export type ConfigKey = (typeof CONFIG_KEYS)[keyof typeof CONFIG_KEYS]; export type ConfigValueKey = keyof ConfigValueMap;