feat(coding): nest Conversations under Agents
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Bot,
|
||||
ChevronRight,
|
||||
CircleAlert,
|
||||
LoaderCircle,
|
||||
MessageSquarePlus,
|
||||
@@ -558,74 +559,106 @@ export function CodingChatPanel({
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">本地 Conversation</p>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-2">
|
||||
<p className="px-2 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground">伙伴</p>
|
||||
<div className="space-y-1">
|
||||
{agents.map((agent) => (
|
||||
<button
|
||||
key={agent.id}
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex min-h-10 w-full items-center gap-2 rounded-xl px-2.5 text-left text-sm transition-[background-color,scale] duration-150 ease-out active:scale-[0.96]',
|
||||
selectedAgent?.id === agent.id ? 'bg-background font-medium shadow-soft' : 'hover:bg-background/70',
|
||||
)}
|
||||
onClick={() => {
|
||||
clearConversationSelection();
|
||||
selectAgent(agent.id);
|
||||
}}
|
||||
>
|
||||
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-foreground text-xs font-semibold text-background">
|
||||
{agent.name.trim().slice(0, 1) || 'A'}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{agent.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4 flex min-h-10 items-center justify-between px-2">
|
||||
<p className="text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground">对话</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10 rounded-xl transition-transform duration-150 ease-out active:scale-[0.96]"
|
||||
aria-label="新建对话"
|
||||
disabled={!selectedAgent || Boolean(selectedAgent && creatingAgentIds[selectedAgent.id])}
|
||||
onClick={() => void handleCreateConversation()}
|
||||
>
|
||||
{selectedAgent && creatingAgentIds[selectedAgent.id]
|
||||
? <LoaderCircle className="h-4 w-4 animate-spin" aria-hidden="true" />
|
||||
: <MessageSquarePlus className="h-4 w-4" aria-hidden="true" />}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{agentConversations.map((conversation) => {
|
||||
const unread = conversationSummaries[conversation.id]?.unread ?? conversation.unread;
|
||||
const summary = conversationSummaries[conversation.id];
|
||||
<p className="px-2 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground">伙伴与对话</p>
|
||||
<div className="space-y-1.5">
|
||||
{agents.map((agent) => {
|
||||
const expanded = selectedAgent?.id === agent.id;
|
||||
const conversationGroupId = `agent-conversations-${agent.id}`;
|
||||
return (
|
||||
<button
|
||||
key={conversation.id}
|
||||
type="button"
|
||||
<div
|
||||
key={agent.id}
|
||||
className={cn(
|
||||
'flex min-h-10 w-full items-center gap-2 rounded-xl px-3 text-left text-sm transition-[background-color,scale] duration-150 ease-out active:scale-[0.96]',
|
||||
targetConversationId === conversation.id
|
||||
? 'bg-background font-medium shadow-soft'
|
||||
: 'text-muted-foreground hover:bg-background/70 hover:text-foreground',
|
||||
'rounded-2xl',
|
||||
expanded && 'bg-background p-1 shadow-soft',
|
||||
)}
|
||||
onClick={() => handleSelectConversation(conversation)}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">{conversation.title}</span>
|
||||
{summary && ['queued', 'running', 'retrying', 'compacting', 'aborting'].includes(summary.runStatus) && (
|
||||
<LoaderCircle className="h-3.5 w-3.5 shrink-0 animate-spin text-brand" aria-label="对话正在运行" />
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex min-h-10 w-full items-center gap-2 rounded-xl px-2.5 text-left text-sm transition-[background-color,scale] duration-150 ease-out active:scale-[0.96]',
|
||||
expanded ? 'font-semibold' : 'hover:bg-background/70',
|
||||
)}
|
||||
aria-label={agent.name}
|
||||
aria-expanded={expanded}
|
||||
aria-controls={conversationGroupId}
|
||||
onClick={() => {
|
||||
clearConversationSelection();
|
||||
selectAgent(agent.id);
|
||||
}}
|
||||
>
|
||||
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-foreground text-xs font-semibold text-background" aria-hidden="true">
|
||||
{agent.name.trim().slice(0, 1) || 'A'}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">{agent.name}</span>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
'h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-150 ease-out',
|
||||
expanded && 'rotate-90',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
{expanded && (
|
||||
<div
|
||||
id={conversationGroupId}
|
||||
role="group"
|
||||
aria-label={`${agent.name} 的对话`}
|
||||
className="mb-1 ml-3 mt-1 border-l border-foreground/10 pl-3 pr-1"
|
||||
>
|
||||
<div className="flex min-h-10 items-center justify-between pl-2">
|
||||
<p className="min-w-0 truncate text-[11px] font-semibold text-muted-foreground">
|
||||
{agent.name} 的对话
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10 rounded-xl transition-transform duration-150 ease-out active:scale-[0.96]"
|
||||
aria-label="新建对话"
|
||||
disabled={Boolean(creatingAgentIds[agent.id])}
|
||||
onClick={() => void handleCreateConversation()}
|
||||
>
|
||||
{creatingAgentIds[agent.id]
|
||||
? <LoaderCircle className="h-4 w-4 animate-spin" aria-hidden="true" />
|
||||
: <MessageSquarePlus className="h-4 w-4" aria-hidden="true" />}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-1 pb-1">
|
||||
{agentConversations.map((conversation) => {
|
||||
const unread = conversationSummaries[conversation.id]?.unread ?? conversation.unread;
|
||||
const summary = conversationSummaries[conversation.id];
|
||||
return (
|
||||
<button
|
||||
key={conversation.id}
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex min-h-10 w-full items-center gap-2 rounded-xl px-3 text-left text-sm transition-[background-color,scale] duration-150 ease-out active:scale-[0.96]',
|
||||
targetConversationId === conversation.id
|
||||
? 'bg-surface-subtle font-medium'
|
||||
: 'text-muted-foreground hover:bg-surface-subtle/70 hover:text-foreground',
|
||||
)}
|
||||
onClick={() => handleSelectConversation(conversation)}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">{conversation.title}</span>
|
||||
{summary && ['queued', 'running', 'retrying', 'compacting', 'aborting'].includes(summary.runStatus) && (
|
||||
<LoaderCircle className="h-3.5 w-3.5 shrink-0 animate-spin text-brand" aria-label="对话正在运行" />
|
||||
)}
|
||||
{unread && <span className="h-2 w-2 shrink-0 rounded-full bg-brand" aria-label="未读" />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{autoCreating && (
|
||||
<div className="flex min-h-10 items-center gap-2 px-3 text-xs text-muted-foreground">
|
||||
<LoaderCircle className="h-3.5 w-3.5 animate-spin" aria-hidden="true" />
|
||||
正在创建首个对话…
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{unread && <span className="h-2 w-2 shrink-0 rounded-full bg-brand" aria-label="未读" />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{autoCreating && (
|
||||
<div className="flex min-h-10 items-center gap-2 px-3 text-xs text-muted-foreground">
|
||||
<LoaderCircle className="h-3.5 w-3.5 animate-spin" aria-hidden="true" />
|
||||
正在创建首个对话…
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
Reference in New Issue
Block a user