feat: implement task management store with IPC integration

- Added a new task store in `src-react/stores/task.ts` to manage tasks and their statuses.
- Implemented functions for creating, executing, and retrying tasks, along with handling task progress and completion.
- Introduced persistence for tasks using IPC.
- Created utility functions for normalizing room types and building subtasks.
- Added a new CSS file for global styles in `src-react/styles.css`.
- Created runtime types in `src-react/types/runtime.ts` and exported them.
- Updated the main entry points for Vue and React applications to support dynamic framework loading.
- Refactored chat model interfaces and utility functions into `src/shared/chat-model.ts`.
- Updated TypeScript configuration to include paths for React components and types.
- Enhanced Vite configuration to support both Vue and React frameworks.
This commit is contained in:
duanshuwen
2026-04-17 07:09:56 +08:00
parent d233b94b2a
commit b1dea9a5c2
68 changed files with 5910 additions and 397 deletions

View File

@@ -0,0 +1,15 @@
import type { LanguageCode } from '../types/runtime';
export const SUPPORTED_LANGUAGE_CODES = ['en', 'zh', 'ja'] as const satisfies readonly LanguageCode[];
export type { LanguageCode };
export const SUPPORTED_LANGUAGES = [
{ code: 'en', label: 'English' },
{ code: 'zh', label: '中文' },
{ code: 'ja', label: '日本語' },
] as const;
export type Namespace = 'common' | 'conversation' | 'setting' | 'window';
export const NAMESPACES = ['common', 'conversation', 'setting', 'window'] as const satisfies readonly Namespace[];

107
src-react/i18n/index.ts Normal file
View File

@@ -0,0 +1,107 @@
import { DEFAULT_LANGUAGE } from '../lib/constants';
import type { LanguageCode } from '../types/runtime';
import type { Namespace } from './constants';
import { detectSystemLanguage, resolveSupportedLanguage } from './resolver';
import { messages, type MessageTree } from './messages';
type InterpolationParams = Record<string, string | number>;
function lookupMessage(source: MessageTree, path: string): unknown {
return path.split('.').reduce<unknown>((current, segment) => {
if (!current || typeof current !== 'object') return undefined;
return (current as Record<string, unknown>)[segment];
}, source);
}
function interpolate(template: string, params?: InterpolationParams): string {
if (!params) return template;
return template.replace(/\{(\w+)\}/g, (_match, token) => {
const value = params[token];
return typeof value === 'undefined' ? `{${token}}` : String(value);
});
}
export interface I18nBridge {
getLocale(): LanguageCode;
setLocale(locale: string | null | undefined): LanguageCode;
t(path: string, params?: InterpolationParams): string;
has(path: string): boolean;
getMessages(locale?: LanguageCode): MessageTree;
subscribe(listener: (locale: LanguageCode) => void): () => void;
}
export function createI18nBridge(initialLocale?: string | null): I18nBridge {
let locale = resolveSupportedLanguage(initialLocale ?? detectSystemLanguage() ?? DEFAULT_LANGUAGE);
const listeners = new Set<(locale: LanguageCode) => void>();
const notify = () => {
for (const listener of listeners) {
listener(locale);
}
};
const api: I18nBridge = {
getLocale() {
return locale;
},
setLocale(nextLocale: string | null | undefined) {
const resolved = resolveSupportedLanguage(nextLocale, locale);
if (resolved !== locale) {
locale = resolved;
notify();
}
return locale;
},
t(path: string, params?: InterpolationParams) {
const localeMessages = messages[locale] ?? messages[DEFAULT_LANGUAGE];
const fallbackMessages = messages[DEFAULT_LANGUAGE];
const translated = lookupMessage(localeMessages, path) ?? lookupMessage(fallbackMessages, path);
if (typeof translated === 'string' || typeof translated === 'number') {
return interpolate(String(translated), params);
}
return path;
},
has(path: string) {
const localeMessages = messages[locale] ?? messages[DEFAULT_LANGUAGE];
const fallbackMessages = messages[DEFAULT_LANGUAGE];
return typeof lookupMessage(localeMessages, path) !== 'undefined' || typeof lookupMessage(fallbackMessages, path) !== 'undefined';
},
getMessages(targetLocale?: LanguageCode) {
return messages[targetLocale ?? locale] ?? messages[DEFAULT_LANGUAGE];
},
subscribe(listener: (locale: LanguageCode) => void) {
listeners.add(listener);
return () => listeners.delete(listener);
},
};
return api;
}
export const i18n = createI18nBridge();
export function setLocale(locale: string | null | undefined): LanguageCode {
return i18n.setLocale(locale);
}
export function getLocale(): LanguageCode {
return i18n.getLocale();
}
export function t(path: string, params?: InterpolationParams): string {
return i18n.t(path, params);
}
export function hasMessage(path: string): boolean {
return i18n.has(path);
}
export function getMessages(locale?: LanguageCode): MessageTree {
return i18n.getMessages(locale);
}
export { SUPPORTED_LANGUAGE_CODES, SUPPORTED_LANGUAGES, NAMESPACES } from './constants';
export type { LanguageCode, Namespace };

112
src-react/i18n/messages.ts Normal file
View File

@@ -0,0 +1,112 @@
import type { LanguageCode } from '../types/runtime';
export interface MessageTree {
[key: string]: string | number | MessageTree;
}
export type I18nMessages = Record<LanguageCode, MessageTree>;
export const messages: I18nMessages = {
en: {
app: {
title: 'ZN-AI',
},
window: {
minimize: 'Minimize',
maximize: 'Maximize',
restore: 'Restore',
close: 'Close',
},
dialog: {
cancel: 'Cancel',
confirm: 'Confirm',
},
theme: {
light: 'Light',
dark: 'Dark',
system: 'System',
},
language: {
zh: 'Chinese',
en: 'English',
ja: 'Japanese',
},
common: {
loading: 'Loading...',
retry: 'Retry',
unknownError: 'Unknown error',
},
conversation: {
newConversation: 'New conversation',
emptyState: 'No messages yet',
},
},
zh: {
app: {
title: 'ZN-AI',
},
window: {
minimize: '最小化',
maximize: '最大化',
restore: '还原',
close: '关闭',
},
dialog: {
cancel: '取消',
confirm: '确认',
},
theme: {
light: '浅色',
dark: '深色',
system: '跟随系统',
},
language: {
zh: '中文',
en: '英文',
ja: '日语',
},
common: {
loading: '加载中...',
retry: '重试',
unknownError: '未知错误',
},
conversation: {
newConversation: '新建对话',
emptyState: '暂无消息',
},
},
ja: {
app: {
title: 'ZN-AI',
},
window: {
minimize: '最小化',
maximize: '最大化',
restore: '復元',
close: '閉じる',
},
dialog: {
cancel: 'キャンセル',
confirm: '確認',
},
theme: {
light: 'ライト',
dark: 'ダーク',
system: 'システムに従う',
},
language: {
zh: '中国語',
en: '英語',
ja: '日本語',
},
common: {
loading: '読み込み中...',
retry: '再試行',
unknownError: '不明なエラー',
},
conversation: {
newConversation: '新しい会話',
emptyState: 'メッセージはまだありません',
},
},
};

View File

@@ -0,0 +1,24 @@
import { SUPPORTED_LANGUAGE_CODES } from './constants';
import type { LanguageCode } from '../types/runtime';
const SUPPORTED_LANGUAGE_SET = new Set<string>(SUPPORTED_LANGUAGE_CODES);
export function normalizeLocale(locale: string | null | undefined): string {
return locale?.trim().toLowerCase().split('_').join('-') ?? '';
}
export function resolveSupportedLanguage(
locale: string | null | undefined,
fallback: LanguageCode = 'zh',
): LanguageCode {
const normalizedLocale = normalizeLocale(locale);
if (!normalizedLocale) return fallback;
const [baseLanguage] = normalizedLocale.split('-');
return SUPPORTED_LANGUAGE_SET.has(baseLanguage) ? (baseLanguage as LanguageCode) : fallback;
}
export function detectSystemLanguage(): LanguageCode {
if (typeof navigator === 'undefined') return 'zh';
return resolveSupportedLanguage(navigator.language);
}