- 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.
109 lines
5.1 KiB
TypeScript
109 lines
5.1 KiB
TypeScript
import type { ChatHistoryBucket } from './types';
|
||
|
||
type ChatHistoryPanelProps = {
|
||
buckets: ChatHistoryBucket[];
|
||
selectedConversationId?: string;
|
||
loading?: boolean;
|
||
onNewChat?: () => void;
|
||
onSelectConversation?: (conversationId: string) => void;
|
||
onRenameConversation?: (conversationId: string) => void;
|
||
onDeleteConversation?: (conversationId: string) => void;
|
||
};
|
||
|
||
export default function ChatHistoryPanel({
|
||
buckets,
|
||
selectedConversationId,
|
||
loading,
|
||
onNewChat,
|
||
onSelectConversation,
|
||
onRenameConversation,
|
||
onDeleteConversation,
|
||
}: ChatHistoryPanelProps) {
|
||
const hasSessions = buckets.some((bucket) => bucket.sessions.length > 0);
|
||
|
||
return (
|
||
<aside className="flex h-full min-h-0 w-full flex-none flex-col transition-all duration-300 md:w-[220px] lg:w-[230px]">
|
||
<div className="flex h-full min-h-0 flex-col rounded-[20px] bg-white p-2 shadow-[0_10px_30px_rgba(15,23,42,0.06)] dark:bg-[#1b1b1d]">
|
||
<div className="flex items-center gap-3 px-2 py-1.5">
|
||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-[#eff6ff] text-sm font-bold text-[#2B7FFF] dark:bg-[#222225]">
|
||
YN
|
||
</div>
|
||
<div className="min-w-0">
|
||
<div className="truncate text-sm font-semibold text-[#171717] dark:text-gray-100">YINIAN</div>
|
||
<div className="truncate text-xs text-[#99A0AE] dark:text-gray-500">对话历史</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
className="mt-2 flex items-center justify-center gap-2 rounded-lg border border-[#E5E8EE] bg-white px-3 py-2.5 text-sm text-[#171717] shadow-sm transition-colors hover:border-[#2B7FFF] hover:text-[#2B7FFF] dark:border-[#2a2a2d] dark:bg-[#1f1f22] dark:text-gray-100 dark:hover:border-[#2B7FFF]"
|
||
onClick={onNewChat}
|
||
>
|
||
<span className="text-lg leading-none">+</span>
|
||
<span>新对话</span>
|
||
</button>
|
||
|
||
<div className="min-h-0 flex-1 overflow-y-auto px-1.5 py-3">
|
||
{loading ? (
|
||
<div className="rounded-lg border border-dashed border-[#dfeaf6] bg-[#f8fbff] px-4 py-6 text-sm text-[#99A0AE] dark:border-[#2a2a2d] dark:bg-[#1f1f22] dark:text-gray-400">
|
||
正在加载会话历史...
|
||
</div>
|
||
) : null}
|
||
{!loading && !hasSessions ? (
|
||
<div className="rounded-lg border border-dashed border-[#dfeaf6] bg-[#f8fbff] px-4 py-6 text-sm text-[#99A0AE] dark:border-[#2a2a2d] dark:bg-[#1f1f22] dark:text-gray-400">
|
||
还没有对话,点击上方“新对话”开始。
|
||
</div>
|
||
) : null}
|
||
{buckets.map((bucket) => (
|
||
<div key={bucket.key} className="mb-3 last:mb-0">
|
||
<div className="px-2 pb-1 text-[11px] font-medium tracking-tight text-gray-400">{bucket.label}</div>
|
||
<ul className="list-none space-y-2">
|
||
{bucket.sessions.map((session) => {
|
||
const isActive = session.conversationId === selectedConversationId;
|
||
|
||
return (
|
||
<li key={session.conversationId}>
|
||
<button
|
||
type="button"
|
||
className={[
|
||
'flex w-full items-center gap-2 rounded-lg px-2 py-2 text-left text-sm transition-colors',
|
||
isActive
|
||
? 'border border-[#E5E8EE] bg-white text-[#171717] shadow-sm dark:border-[#2a2a2d] dark:bg-[#1f1f22] dark:text-gray-100'
|
||
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700/50',
|
||
].join(' ')}
|
||
onClick={() => onSelectConversation?.(session.conversationId)}
|
||
>
|
||
<span className="h-2 w-2 flex-none rounded-full bg-[#BEDBFF]" />
|
||
<span className="min-w-0 flex-1 truncate">{session.title}</span>
|
||
<span className="shrink-0 text-[11px] text-[#99A0AE] dark:text-gray-500">{session.updatedAt}</span>
|
||
</button>
|
||
{isActive ? (
|
||
<div className="mt-1 flex items-center justify-end gap-2 pr-2 text-[11px]">
|
||
<button
|
||
type="button"
|
||
className="text-[#99A0AE] transition-colors hover:text-[#2B7FFF] dark:text-gray-500"
|
||
onClick={() => onRenameConversation?.(session.conversationId)}
|
||
>
|
||
重命名
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="text-[#99A0AE] transition-colors hover:text-[#ef4444] dark:text-gray-500"
|
||
onClick={() => onDeleteConversation?.(session.conversationId)}
|
||
>
|
||
删除
|
||
</button>
|
||
</div>
|
||
) : null}
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
);
|
||
}
|