refactor: reorganize imports and update type references across services

- Updated import paths for types and utilities in config-service, menu-service, script-execution-service, script-store-service, and window-service to reflect new structure.
- Moved utility functions like debounce and cloneDeep to shared utilities.
- Created new runtime and script types files to centralize type definitions.
- Removed deprecated task-types and locale messages files.
- Updated constants to use shared definitions and added new placeholders for providers.
- Enhanced provider type information with additional placeholders for different languages.
This commit is contained in:
duanshuwen
2026-04-21 23:25:51 +08:00
parent 97a956ffe8
commit 721344883f
24 changed files with 181 additions and 670 deletions

View File

@@ -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");

View File

@@ -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";

View File

@@ -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';

View File

@@ -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: 'ออก',
},
},
};

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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'

View File

@@ -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();

View File

@@ -7,7 +7,7 @@ import type {
ScriptMetaItem,
ScriptsMeta,
ScriptSaveInput,
} from '@runtime/lib/script-types';
} from '../../types/script-types';
const META_FILENAME = 'scripts.meta.json';

View File

@@ -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'

View File

@@ -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;

View File

@@ -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<string, RuntimeMessageTree> = runtimeLocaleMessages

11
global.d.ts vendored
View File

@@ -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<string, string>; body?: unknown }]
return: Promise<unknown>
}
// 任务事件
[IPC_EVENTS.TASK_PROGRESS]: { params: [payload: any]; return: void; }

View File

@@ -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;

View File

@@ -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,
};

View File

@@ -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<ProviderTypeInfo, 'placeholder' | 'placeholderZh' | 'placeholderTh'> | 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<ProviderTypeInfo, 'showModelId' | 'showModelIdInDevModeOnly'> | undefined,
devModeUnlocked: boolean,

View File

@@ -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;
}

View File

@@ -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',
},
},
};

View File

@@ -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';

View File

@@ -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<string, string>;
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<string, string>;
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<ProviderTypeInfo, 'docsUrl' | 'docsUrlZh'> | 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<ProviderTypeInfo, 'placeholder' | 'placeholderZh' | 'placeholderTh'> | 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<ProviderTypeInfo, 'showModelId' | 'showModelIdInDevModeOnly'> | undefined,
devModeUnlocked: boolean,
): boolean {
if (!provider?.showModelId) return false;
if (provider.showModelIdInDevModeOnly && !devModeUnlocked) return false;
return true;
}
export function resolveProviderModelForSave(
provider: Pick<ProviderTypeInfo, 'defaultModelId' | 'showModelId' | 'showModelIdInDevModeOnly'> | 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';

View File

@@ -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<string, unknown> | 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';

View File

@@ -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;