完善客户端模块与工作区能力
This commit is contained in:
@@ -671,10 +671,10 @@ function MessageBubble({
|
||||
<span>{deliveryLabel}</span>
|
||||
</div>
|
||||
)}
|
||||
<p className="whitespace-pre-wrap break-words break-all text-sm">{text}</p>
|
||||
<p className="chat-message-body whitespace-pre-wrap break-words break-all">{text}</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="chat-assistant-message-markdown prose prose-sm max-w-none break-words break-all text-foreground">
|
||||
<div className="chat-message-body chat-assistant-message-markdown prose max-w-none break-words break-all text-foreground">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[[rehypeKatex, { strict: false, throwOnError: false, output: 'html' }]]}
|
||||
|
||||
@@ -934,6 +934,7 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
|
||||
const [composerStopArmed, setComposerStopArmed] = useState(true);
|
||||
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
|
||||
const [startingAgentId, setStartingAgentId] = useState<string | null>(null);
|
||||
const agentSelectionRequestRef = useRef(0);
|
||||
const transcriptScrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const messageElementsRef = useRef(new Map<string, HTMLDivElement>());
|
||||
const undoOriginatingDraftRef = useRef('');
|
||||
@@ -1233,7 +1234,7 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
|
||||
const responseWaitElapsedSeconds = responseWaitStartedAt
|
||||
? Math.max(0, Math.floor((responseWaitNow - responseWaitStartedAt) / 1000))
|
||||
: 0;
|
||||
const selectedProjectAgentAvatarSrc = selectedProjectAgent ? getAgentAvatarSrc(selectedProjectAgent.avatarId) : undefined;
|
||||
const selectedProjectAgentAvatarSrc = selectedProjectAgent ? getAgentAvatarSrc(selectedProjectAgent.avatarId, selectedProjectAgent.avatarDataUrl) : undefined;
|
||||
const selectedProjectAgentAvatarAlt = selectedProjectAgent ? `${selectedProjectAgent.name || selectedProjectAgent.roleName}头像` : '';
|
||||
const selectedSessionDiff = useMemo(() => {
|
||||
if (!selectedDiffPath) return null;
|
||||
@@ -2326,7 +2327,9 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
|
||||
await abortSession(selectedSessionId);
|
||||
}, [abortSession, composerStopArmed, selectedSessionId]);
|
||||
|
||||
const handleSelectAgent = useCallback(async (agentId: string) => {
|
||||
const handleSelectAgent = useCallback((agentId: string) => {
|
||||
const requestId = agentSelectionRequestRef.current + 1;
|
||||
agentSelectionRequestRef.current = requestId;
|
||||
if (!agentId) {
|
||||
setSelectedAgentId(null);
|
||||
clearSessionSelection();
|
||||
@@ -2339,18 +2342,27 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
|
||||
}
|
||||
setSelectedAgentId(agentId);
|
||||
setStartingAgentId(agentId);
|
||||
try {
|
||||
const latestSession = conversationState.sessions
|
||||
.filter((item) => item.agentId === agentId && !item.archivedAt)
|
||||
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
||||
if (latestSession) {
|
||||
await selectSession(latestSession.sessionId);
|
||||
} else {
|
||||
clearSessionSelection();
|
||||
}
|
||||
} finally {
|
||||
setStartingAgentId((current) => current === agentId ? null : current);
|
||||
}
|
||||
// Let the selected card paint before loading the next transcript. The
|
||||
// synchronous store update otherwise makes the sidebar wait behind the
|
||||
// chat panel's session swap during the same click task.
|
||||
window.setTimeout(() => {
|
||||
if (agentSelectionRequestRef.current !== requestId) return;
|
||||
void (async () => {
|
||||
try {
|
||||
const latestSession = conversationState.sessions
|
||||
.filter((item) => item.agentId === agentId && !item.archivedAt)
|
||||
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
||||
if (agentSelectionRequestRef.current !== requestId) return;
|
||||
if (latestSession) {
|
||||
await selectSession(latestSession.sessionId);
|
||||
} else {
|
||||
clearSessionSelection();
|
||||
}
|
||||
} finally {
|
||||
setStartingAgentId((current) => current === agentId ? null : current);
|
||||
}
|
||||
})();
|
||||
}, 0);
|
||||
}, [clearSessionSelection, conversationState.sessions, selectedAgentId, selectSession]);
|
||||
|
||||
const handleSelectConversation = useCallback(async (sessionId: string) => {
|
||||
@@ -2370,6 +2382,7 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
|
||||
const agent: ProjectAgentConfig = {
|
||||
id: `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
avatarId: input.avatarId,
|
||||
avatarDataUrl: input.avatarDataUrl,
|
||||
roleName: '项目伙伴',
|
||||
name: input.name,
|
||||
builtIn: false,
|
||||
@@ -2705,10 +2718,25 @@ export function OpencodeChatPanel({ variant = 'main', navigationDraft, onOpenPro
|
||||
return (
|
||||
<Fragment key={messageKey}>
|
||||
{startingExecutionRun && graphScopedAssistant ? executionTranscriptNode : (
|
||||
<>
|
||||
{executionTranscriptNode}
|
||||
{messageTranscriptNode}
|
||||
</>
|
||||
executionTranscriptNode && messageTranscriptNode ? (
|
||||
<div
|
||||
className="space-y-0"
|
||||
data-testid="chat-execution-reply-group"
|
||||
>
|
||||
{executionTranscriptNode}
|
||||
<div
|
||||
className="mt-1 h-px w-full bg-border/70"
|
||||
data-testid="chat-execution-reply-divider"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{messageTranscriptNode}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{executionTranscriptNode}
|
||||
{messageTranscriptNode}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
{assetReviewInvocation && (
|
||||
<div className="mt-3 max-w-[80%] sm:ml-11">
|
||||
|
||||
Reference in New Issue
Block a user