fix: isolate teacher references from unrelated selections

This commit is contained in:
鲨鱼辣椒 committed 2026-09-30 11:19:37 +08:00
1 parent c55e9c7182
commit 36202e0642
6 files changed
+235 -11

No files matched your search

+25 -8
View File
@@ -191,8 +191,13 @@ export function CodingChatPanel({
const [teacherComposing, setTeacherComposing] = useState(false);
const [consultationWidth, setConsultationWidth] = useState(508);
const account = useAuthStore((state) => state.user?.userId ?? state.user?.username ?? 'signed-out');
const [teacherQuote, setTeacherQuote] = useState<TeacherReference | undefined>();
const [teacherQuoteSource, setTeacherQuoteSource] = useState<string | null>(null);
const operationChatRef = useRef<HTMLDivElement>(null);
const [teacherQuote, setTeacherQuote] = useState<{
reference: TeacherReference; account: string; projectId: string; sourceId: string; agentId: string;
}>();
const consumeTeacherQuote = useCallback((reference: TeacherReference) => {
setTeacherQuote(current => current?.reference === reference ? undefined : current);
}, []);
const appliedNavigationDraftRef = useRef<string | null>(null);
const selectedConversationContextRef = useRef<string | null>(null);
const attachmentsRef = useRef(attachmentsByDraftKey);
@@ -251,9 +256,18 @@ export function CodingChatPanel({
const [teacherIntroduction, setTeacherIntroduction] = useState<HTMLDivElement | null>(null);
const openTeacher = (agentId?: string) => {
if (agentId) teacherCompanion.selectAgent(agentId);
const selectedText = window.getSelection()?.toString().trim();
setTeacherQuote(selectedText ? { kind: 'code', text: selectedText.slice(0, 12000) } : undefined);
setTeacherQuoteSource(`${activeProject?.id}:${targetConversationId}`);
const selection = window.getSelection();
const operationChat = operationChatRef.current;
// Avatar navigation must not turn selected text in another teacher's chat into a quote.
const fromOperationChat = operationChat && !operationChat.hidden && selection?.rangeCount
&& Array.from({ length: selection.rangeCount }, (_, index) => selection.getRangeAt(index))
.every(range => operationChat.contains(range.commonAncestorContainer));
const selectedText = fromOperationChat ? selection.toString().trim() : '';
setTeacherQuote(selectedText && activeProject && targetConversationId ? {
reference: { kind: 'code', text: selectedText.slice(0, 12000) }, account,
projectId: activeProject.id, sourceId: targetConversationId,
agentId: agentId ?? teacherCompanion.selectedAgent?.teacher_id ?? 'legacy',
} : undefined);
setConsultationOpen(true);
setAgentBrowserOpen(true);
};
@@ -313,7 +327,7 @@ export function CodingChatPanel({
useEffect(() => {
setAgentBrowserOpen(false);
}, [activeProject?.id]);
useEffect(() => { setTeacherQuote(undefined); }, [activeProject?.id, targetConversationId]);
useEffect(() => { setTeacherQuote(undefined); }, [account, activeProject?.id, targetConversationId]);
useEffect(() => () => disconnectEvents(), [disconnectEvents]);
@@ -812,7 +826,7 @@ export function CodingChatPanel({
}} />
</div>
<CodingChatBody welcome={showWelcome} hidden={agentBrowserOpen} bubble={teacherBubble}>
<div id="classroom-chat-view" role="tabpanel" aria-labelledby="classroom-chat-tab" hidden={agentBrowserOpen} className="classroom-chat-view min-h-0 flex-1 flex-col">
<div ref={operationChatRef} id="classroom-chat-view" role="tabpanel" aria-labelledby="classroom-chat-tab" hidden={agentBrowserOpen} className="classroom-chat-view min-h-0 flex-1 flex-col">
{(workspaceError || conversationMetadataError || connectionError) && (
<div className="mx-4 mt-3 flex min-h-10 items-center gap-2 rounded-xl bg-destructive/5 px-3 py-2 text-xs text-destructive sm:mx-5">
@@ -984,7 +998,10 @@ export function CodingChatPanel({
externalTopic={teacherCompanion.topic}
onTopicChange={teacherCompanion.noteTopic}
onComposingChange={setTeacherComposing}
quote={consultationOpen && teacherQuoteSource === `${activeProject.id}:${targetConversationId}` ? teacherQuote : undefined} />
quote={consultationOpen && teacherQuote?.account === account && teacherQuote.projectId === activeProject.id
&& teacherQuote.sourceId === targetConversationId
&& teacherQuote.agentId === (teacherCompanion.selectedAgent?.teacher_id ?? 'legacy') ? teacherQuote.reference : undefined}
onQuoteConsumed={consumeTeacherQuote} />
</div>
)}
</section>
+9 -3
View File
@@ -43,6 +43,7 @@ function ProjectTeacherChatPanel({
sourceId,
onBringBack,
quote,
onQuoteConsumed,
draftRevision,
sampleContext,
externalTopic,
@@ -56,6 +57,7 @@ function ProjectTeacherChatPanel({
sourceId: string;
onBringBack?(text: string): void;
quote?: TeacherReference;
onQuoteConsumed?(reference: TeacherReference): void;
draftRevision?: number;
sampleContext?: string;
projectName?: string;
@@ -84,7 +86,7 @@ function ProjectTeacherChatPanel({
return selectedAgent ? JSON.parse(localStorage.getItem(`makelore-consultation-draft:${account}:${projectId}:agent:${selectedAgent.teacher_id}`) ?? '{}') : {};
} catch { return {}; }
});
const [referenceScope, setReferenceScope] = useState({ projectId: saved.projectId ?? projectId, sourceId: saved.sourceId ?? sourceId });
const [referenceScope, setReferenceScope] = useState(quote ? { projectId, sourceId } : { projectId: saved.projectId ?? projectId, sourceId: saved.sourceId ?? sourceId });
const referenceScopeRef = useRef(referenceScope);
referenceScopeRef.current = referenceScope;
const [legacyDraft] = useState<{ text?: string; references?: TeacherReference[] }>(() => {
@@ -211,8 +213,12 @@ function ProjectTeacherChatPanel({
}
}, [busy]);
useEffect(() => {
if (quote) { setReferences([quote]); setReferenceScope({ projectId, sourceId }); }
}, [quote, projectId, sourceId]);
if (quote) {
setReferences([quote]);
setReferenceScope({ projectId, sourceId });
onQuoteConsumed?.(quote);
}
}, [quote, projectId, sourceId, onQuoteConsumed]);
useEffect(() => {
let alive = true;
setBusy(true);