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,92 @@
import { useEffect, useRef } from 'react';
import type { ChatMessageItem } from './types';
type ChatMessageListProps = {
messages: ChatMessageItem[];
loading?: boolean;
};
export default function ChatMessageList({ messages, loading }: ChatMessageListProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
container.scrollTop = container.scrollHeight;
}, [messages]);
return (
<div className="flex min-h-0 flex-1 flex-col overflow-hidden px-6 py-6">
<div ref={containerRef} className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto pr-1">
{loading ? (
<div className="rounded-[18px] border border-dashed border-[#BEDBFF] bg-[#EFF6FF] px-4 py-3 text-sm text-[#525866] dark:border-[#2a2a2d] dark:bg-[#1f1f22] dark:text-gray-400">
...
</div>
) : null}
{!loading && messages.length === 0 ? (
<div className="rounded-[18px] border border-dashed border-[#BEDBFF] bg-[#EFF6FF] px-4 py-6 text-sm leading-7 text-[#525866] dark:border-[#2a2a2d] dark:bg-[#1f1f22] dark:text-gray-400">
</div>
) : null}
{messages.map((message) => (
<article
key={message.id}
className={[
'flex gap-3 rounded-[18px] border p-4',
message.role === 'assistant'
? 'border-[#E5E8EE] bg-[#f8fbff] dark:border-[#2a2a2d] dark:bg-[#1f1f22]'
: 'border-[#dfeaf6] bg-white dark:border-[#2a2a2d] dark:bg-[#232327]',
].join(' ')}
>
<div className="flex h-10 w-10 flex-none items-center justify-center rounded-full bg-[#eff6ff] text-sm font-bold text-[#2B7FFF] dark:bg-[#222225]">
{message.role === 'assistant' ? 'AI' : 'ME'}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-3">
<div className="text-sm font-semibold text-[#171717] dark:text-gray-100">{message.name}</div>
<div className="text-xs text-[#99A0AE] dark:text-gray-500">{message.time}</div>
</div>
<p className="mt-2 whitespace-pre-wrap text-sm leading-7 text-[#525866] dark:text-gray-300">{message.content}</p>
{message.attachments && message.attachments.length > 0 ? (
<div className="mt-3 flex flex-wrap gap-2">
{message.attachments.map((attachment, index) => {
const attachmentKey = attachment.filePath || `${attachment.fileName}-${index}`;
const isImage = attachment.mimeType.startsWith('image/') && Boolean(attachment.preview);
if (isImage && attachment.preview) {
return (
<div
key={attachmentKey}
className="overflow-hidden rounded-xl border border-[#E5E8EE] bg-white dark:border-[#2a2a2d] dark:bg-[#232327]"
>
<img
alt={attachment.fileName}
className="h-24 w-24 object-cover"
src={attachment.preview}
/>
</div>
);
}
return (
<div
key={attachmentKey}
className="rounded-full border border-[#E5E8EE] px-3 py-1.5 text-xs text-[#525866] dark:border-[#2a2a2d] dark:text-gray-300"
>
{attachment.fileName}
</div>
);
})}
</div>
) : null}
{message.isStreaming ? (
<div className="mt-3 text-xs text-[#2B7FFF]">...</div>
) : null}
</div>
</article>
))}
</div>
</div>
);
}