merge: retain agent refresh in the updated teacher trial

This commit is contained in:
鲨鱼辣椒 committed 2026-09-28 14:09:50 +08:00
commit 9adab45388
14 files changed
+527 -60

No files matched your search

+47 -25
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { ArrowUp, ChevronDown, ChevronRight, Loader2 } from 'lucide-react';
import { useAuthStore } from '@/stores/auth';
import { teacherApi, teacherTopicsPath, legacyTopicBase } from '@/lib/coding-teacher';
import { teacherApi, teacherTopicsPath, legacyTopicBase, type TeacherCatalogState } from '@/lib/coding-teacher';
import type {
TeacherDefinition,
TeacherCatalog,
@@ -31,6 +31,8 @@ export function TeacherChatPanel({
onTopicChange,
onComposingChange,
selectedAgent,
catalogState,
reloadCatalog,
}: {
projectId: string;
sourceId: string;
@@ -43,6 +45,8 @@ export function TeacherChatPanel({
onTopicChange?(topic: TeacherTopic): void;
onComposingChange?(composing: boolean): void;
selectedAgent?: TeacherCatalog['items'][number];
catalogState?: TeacherCatalogState;
reloadCatalog?(): Promise<void>;
}) {
const base = teacherTopicsPath(projectId);
const account = useAuthStore((state) => state.user?.userId ?? state.user?.username ?? 'signed-out');
@@ -63,7 +67,9 @@ export function TeacherChatPanel({
}, [draftKey]);
const label = '智能体';
const [historyOpen, setHistoryOpen] = useState(false);
const [definition, setDefinition] = useState<TeacherDefinition | null>(null);
const [localDefinition, setDefinition] = useState<TeacherDefinition | null>(null);
const catalogStateRef = useRef(catalogState);
catalogStateRef.current = catalogState;
const externalTopicRef = useRef(externalTopic);
externalTopicRef.current = externalTopic;
const selectedAgentId = selectedAgent?.teacher_id;
@@ -83,10 +89,20 @@ export function TeacherChatPanel({
draftRef.current = { text, references };
const [error, setError] = useState('');
const [busy, setBusy] = useState(true);
const [enabled, setEnabled] = useState(false);
const [legacyEnabled, setLegacyEnabled] = useState(false);
const [teachers, setTeachers] = useState<TeacherCatalog['items']>([]);
const [selectedTeacherVersion, setSelectedTeacherVersion] = useState<number>();
const [localEnabled, setEnabled] = useState(false);
const [localLegacyEnabled, setLegacyEnabled] = useState(false);
const [localTeachers, setTeachers] = useState<TeacherCatalog['items']>([]);
const [localTeacherVersion, setSelectedTeacherVersion] = useState<number>();
// Share the current catalog with the topbar without reloading the topic or its draft.
const teachers = catalogState?.catalog.items ?? localTeachers;
const selectedTeacher = selectedAgentId ? teachers.find((item) => item.teacher_id === selectedAgentId)
: teachers.find((item) => item.is_default) ?? teachers[0];
const enabled = catalogState ? (selectedAgentId ? Boolean(selectedTeacher)
: teachers.length > 0 || (catalogState.config.enabled && !catalogState.config.definition?.config_id)) : localEnabled;
const legacyEnabled = catalogState?.config.enabled ?? localLegacyEnabled;
const selectedTeacherVersion = catalogState ? selectedTeacher?.version : localTeacherVersion;
const definition = topic?.definition ?? (catalogState
? selectedTeacher?.definition ?? selectedAgent?.definition ?? catalogState.config.definition : localDefinition);
const topicEnabled = legacyFriend ? false : !topic ? enabled
: topic.definition.config_id
? teachers.some((item) => item.teacher_id === topic.definition.config_id)
@@ -137,25 +153,31 @@ export function TeacherChatPanel({
setBusy(true);
void (async () => {
try {
const config = draftRevision
? { definition: (await teacherApi.preview(draftRevision)).payload, enabled: true }
: await teacherApi.config();
if (alive) {
setDefinition(config.definition);
setEnabled(config.enabled);
setLegacyEnabled(config.enabled);
}
if (!draftRevision) {
const catalog = await teacherApi.catalog();
if (!draftRevision && reloadCatalog) {
// Opening the panel still checks for updates through the shared loader.
// A failed background read leaves the last usable catalog in place.
await reloadCatalog().catch(() => undefined);
} else if (draftRevision || !catalogStateRef.current) {
const config = draftRevision
? { definition: (await teacherApi.preview(draftRevision)).payload, enabled: true }
: await teacherApi.config();
if (alive) {
setTeachers(catalog.items);
const selected = selectedAgentId
? catalog.items.find((item) => item.teacher_id === selectedAgentId)
: catalog.items.find((item) => item.is_default) ?? catalog.items[0];
setSelectedTeacherVersion(selected?.version);
setEnabled(selectedAgentId ? Boolean(selected) : catalog.items.length > 0 || (config.enabled && !config.definition?.config_id));
const visibleAgent = selected ?? selectedAgentRef.current;
if (visibleAgent) setDefinition(visibleAgent.definition);
setDefinition(config.definition);
setEnabled(config.enabled);
setLegacyEnabled(config.enabled);
}
if (!draftRevision) {
const catalog = await teacherApi.catalog();
if (alive) {
setTeachers(catalog.items);
const selected = selectedAgentId
? catalog.items.find((item) => item.teacher_id === selectedAgentId)
: catalog.items.find((item) => item.is_default) ?? catalog.items[0];
setSelectedTeacherVersion(selected?.version);
setEnabled(selectedAgentId ? Boolean(selected) : catalog.items.length > 0 || (config.enabled && !config.definition?.config_id));
const visibleAgent = selected ?? selectedAgentRef.current;
if (visibleAgent) setDefinition(visibleAgent.definition);
}
}
}
} catch (e) {
@@ -190,7 +212,7 @@ export function TeacherChatPanel({
// eslint-disable-next-line react-hooks/exhaustive-deps
generation.current++;
};
}, [base, draftRevision, label, consume, selectedAgentId]);
}, [base, draftRevision, label, consume, selectedAgentId, reloadCatalog]);
useEffect(() => {
if (!topic?.id || legacyFriend) return;
const streamTopicId = topic.id;