fix(chat): reconcile reply recovery with continuous agent conversations
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { ArrowUp, ChevronDown, ChevronRight, Loader2 } from 'lucide-react';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { teacherApi, teacherTopicsPath, legacyTopicBase, type TeacherCatalogState } from '@/lib/coding-teacher';
|
||||
@@ -101,9 +101,7 @@ export function TeacherChatPanel({
|
||||
const focusedOnce = useRef(false);
|
||||
const [topics, setTopics] = useState<TeacherTopicList>({ items: [], lastSelectedTopicId: null });
|
||||
const [storedTopic, setTopic] = useState<TeacherTopic | null>(cachedView?.topic ?? null);
|
||||
const topic = useMemo(() => storedTopic && continuous ? { ...storedTopic,
|
||||
discussion: storedTopic.projectId === projectId ? storedTopic.discussion : storedTopic.conversation?.discussions[projectId] }
|
||||
: storedTopic, [storedTopic, continuous, projectId]);
|
||||
const topic = storedTopic;
|
||||
const legacyFriend = topic?.role === 'friend';
|
||||
const legacyView = Boolean(continuous && topic && !topic.conversation);
|
||||
const viewingLegacy = useRef(false);
|
||||
@@ -383,15 +381,18 @@ export function TeacherChatPanel({
|
||||
if (version === generation.current) setBusy(false);
|
||||
}
|
||||
};
|
||||
const retryScopeMatches = (request: TeacherRequest) => !continuous
|
||||
|| (request.projectId === projectId && (request.sourceConversationId ?? '') === sourceId);
|
||||
const send = async (action?: Pick<TeacherSend, 'text' | 'intent'>, retry?: TeacherRequest) => {
|
||||
// A historical retry stays with its original topic and published Agent.
|
||||
if (retry && (topicRef.current?.id !== topic?.id || topic?.projectId !== projectId
|
||||
if (retry && (topicRef.current?.id !== topic?.id || !topic || !retryScopeMatches(retry)
|
||||
|| (!continuous && topic.projectId !== projectId)
|
||||
|| (selectedAgentId && topic.definition.config_id !== selectedAgentId)
|
||||
|| !topic.requests.some((request) => request === retry && request.status === 'completed' && request.replyIncomplete))) return;
|
||||
const question = action?.text ?? text;
|
||||
const questionReferences = retry?.references ?? (action ? [] : references);
|
||||
const questionSourceId = retry
|
||||
? retry.sourceConversationId ?? (topic?.sourceConversationId === 'project' ? undefined : topic?.sourceConversationId)
|
||||
? retry.sourceConversationId ?? (continuous || topic?.sourceConversationId === 'project' ? undefined : topic?.sourceConversationId)
|
||||
: sourceId;
|
||||
if (!question.trim() || busy || sendInFlight.current || !topicEnabled || topicRef.current?.requests.some((request) => ['preparing', 'running'].includes(request.status))) return;
|
||||
if (continuous && !retry && questionReferences.length && (referenceScope.projectId !== projectId || referenceScope.sourceId !== sourceId)) {
|
||||
@@ -423,8 +424,8 @@ export function TeacherChatPanel({
|
||||
// Copy changes must not create another charge for an uncertain hint request.
|
||||
const retrySuggestions = !retry && action?.intent === 'suggestions' && previous?.intent === 'suggestions';
|
||||
const input =
|
||||
previous && (!continuous || previous.projectId === projectId)
|
||||
&& previous.sourceConversationId === (questionSourceId || undefined) && (retrySuggestions || (
|
||||
previous && (!continuous || (previous.projectId === projectId
|
||||
&& previous.sourceConversationId === (questionSourceId || undefined))) && (retrySuggestions || (
|
||||
previous.text === question &&
|
||||
(previous.intent ?? 'question') === (action?.intent ?? 'question') &&
|
||||
JSON.stringify(previous.references) === JSON.stringify(questionReferences) &&
|
||||
@@ -523,7 +524,8 @@ export function TeacherChatPanel({
|
||||
</details>}
|
||||
{request.status === 'completed' && request.replyIncomplete ? <div className="space-y-3">
|
||||
<p role="status" className="text-[13px] leading-7 text-muted-foreground">这次回复未能完整显示</p>
|
||||
{request.intent !== 'check-in' && request.text.trim() && <button type="button" disabled={helpUnavailable} onClick={() => void send({ text: request.text, intent: request.intent }, request)} className="consultation-retry">重新回答</button>}
|
||||
{request.intent !== 'check-in' && request.text.trim() && <button type="button" disabled={helpUnavailable || !retryScopeMatches(request)} onClick={() => void send({ text: request.text, intent: request.intent }, request)} className="consultation-retry">重新回答</button>}
|
||||
{!retryScopeMatches(request) && <p className="text-xs text-muted-foreground">回到原项目和操作对话后,可以重新回答。</p>}
|
||||
</div> : request.intent === 'suggestions' ? <>
|
||||
{['running', 'preparing'].includes(request.status)
|
||||
? <p role="status" className="flex items-center gap-2 text-[13px] leading-7 text-muted-foreground"><Loader2 className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />我看看你最近做到了哪里…</p>
|
||||
|
||||
Reference in New Issue
Block a user