Files
zn-ai/src-react/components/chat/ChatHistoryPanel.tsx
duanshuwen b1dea9a5c2 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.
2026-04-17 07:09:56 +08:00

109 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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