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

@@ -1,167 +1 @@
/// 附件文件元数据(与 ClawX 对齐)
export interface AttachedFileMeta {
fileName: string;
mimeType: string;
fileSize: number;
preview: string | null;
filePath?: string;
source?: 'user-upload' | 'tool-result' | 'message-ref';
}
/// 内容块(与 ClawX 对齐,用于未来扩展结构化消息)
export interface ContentBlock {
type: 'text' | 'image' | 'thinking' | 'tool_use' | 'tool_result' | 'toolCall' | 'toolResult';
text?: string;
thinking?: string;
source?: { type: string; media_type?: string; data?: string; url?: string };
data?: string;
mimeType?: string;
id?: string;
name?: string;
input?: unknown;
arguments?: unknown;
content?: unknown;
}
/// 原始消息(与 ClawX RawMessage 对齐)
export interface RawMessage {
role: 'user' | 'assistant' | 'system' | 'toolresult';
content: string | ContentBlock[];
timestamp?: number;
id?: string;
toolCallId?: string;
toolName?: string;
details?: unknown;
isError?: boolean;
/** zn-ai 特有:问题标签(保留现有能力) */
question?: string[];
/** zn-ai 特有:工具调用结果(保留现有能力) */
toolCall?: any;
/** 本地-only附件 */
_attachedFiles?: AttachedFileMeta[];
}
/// 工具状态(与 ClawX 对齐)
export interface ToolStatus {
id?: string;
toolCallId?: string;
name: string;
status: 'running' | 'completed' | 'error';
durationMs?: number;
summary?: string;
updatedAt: number;
}
/// 会话(与 ClawX ChatSession 对齐)
export interface ChatSession {
key: string;
label?: string;
displayName?: string;
thinkingLevel?: string;
model?: string;
updatedAt?: number;
}
/// 流式消息辅助:从 RawMessage 提取纯文本
export function extractText(message?: RawMessage | null): string {
if (!message) return '';
const content = message.content;
if (typeof content === 'string') return content;
if (Array.isArray(content)) {
return (content as Array<{ type?: string; text?: string }>)
.filter((b) => b.type === 'text' && b.text)
.map((b) => b.text!)
.join('\n');
}
return '';
}
/// 流式消息辅助:从 RawMessage 提取 thinking 文本
export function extractThinking(message?: RawMessage | null): string | null {
if (!message) return null;
const content = message.content;
if (Array.isArray(content)) {
const block = content.find((b: any) => b.type === 'thinking');
return block?.thinking || null;
}
return null;
}
/// 流式消息辅助:从 RawMessage 提取图片
export function extractImages(message?: RawMessage | null): Array<{ url?: string; data?: string; mimeType: string }> {
if (!message) return [];
const content = message.content;
if (!Array.isArray(content)) return [];
const images: Array<{ url?: string; data?: string; mimeType: string }> = [];
for (const block of content as ContentBlock[]) {
if (block.type === 'image') {
if (block.source) {
const src = block.source;
if (src.type === 'base64' && src.data) {
images.push({ data: src.data, mimeType: src.media_type || 'image/jpeg' });
} else if (src.type === 'url' && src.url) {
images.push({ url: src.url, mimeType: src.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') && block.content) {
images.push(...extractImages({ role: 'toolresult', content: block.content }));
}
}
return images;
}
/// 流式消息辅助:从 RawMessage 提取 tool_use
export function extractToolUse(message?: RawMessage | null): Array<{ id?: string; name: string; input?: unknown }> {
if (!message) return [];
const content = message.content;
if (!Array.isArray(content)) return [];
return (content as ContentBlock[])
.filter((b) => b.type === 'tool_use' || b.type === 'toolCall')
.map((b) => ({ id: b.id, name: b.name || b.id || 'tool', input: b.input ?? b.arguments }));
}
/// 格式化时间戳(秒/ms 兼容)
export function formatTimestamp(ts?: number): string {
if (!ts) return '';
const ms = ts < 1e12 ? ts * 1000 : ts;
return new Date(ms).toLocaleString();
}
/// 判断是否为 tool-only 消息
export function isToolOnlyMessage(message?: RawMessage): boolean {
if (!message) return false;
const role = message.role;
if (role === 'toolresult' || role === 'tool_result') return true;
const content = message.content;
if (Array.isArray(content)) {
const hasTool = content.some((b: any) =>
['tool_use', 'tool_result', 'toolCall', 'toolResult'].includes(b.type)
);
const hasText = content.some(
(b: any) => b.type === 'text' && b.text?.trim?.()
);
const hasImage = content.some((b: any) => b.type === 'image');
return hasTool && !hasText && !hasImage;
}
return false;
}
/// 判断是否为 tool result 角色
export function isToolResultRole(role?: string): boolean {
if (!role) return false;
const normalized = role.toLowerCase();
return normalized === 'toolresult' || normalized === 'tool_result';
}
/// 判断是否为内部消息(不应展示在 UI
export function isInternalMessage(msg: { role?: string; content?: unknown }): boolean {
if (msg.role === 'system') return true;
if (msg.role === 'assistant') {
const text = typeof msg.content === 'string' ? msg.content : extractText(msg as RawMessage);
if (/^(HEARTBEAT_OK|NO_REPLY)\s*$/.test(text)) return true;
}
return false;
}
export * from '@shared/chat-model';