fix(teacher): 独立刷新老师目录以缩短配置更新延迟

This commit is contained in:
2026-09-29 12:03:06 +08:00
parent 2767a15017
commit 913ccad577
9 changed files with 184 additions and 7 deletions

View File

@@ -37,6 +37,7 @@ export const teacherApi = {
sendConversation: (projectId: string, agentId: string, input: TeacherSend) => hostApiFetch<TeacherTopic>(
teacherConversationsPath(projectId) + '/' + encodeURIComponent(agentId) + '/messages', { method: 'POST', body: JSON.stringify(input) }),
catalog: () => hostApiFetch<TeacherCatalog>('/api/coding/teacher/teachers'),
catalogRevision: () => hostApiFetch<{ revision: number }>('/api/coding/teacher/teachers/revision'),
checkIn: (projectId: string, input: TeacherCheckInInput) =>
hostApiFetch<TeacherCheckInResult>(`/api/coding/projects/${encodeURIComponent(projectId)}/teacher-check-in`, {
method: 'POST',

View File

@@ -141,11 +141,14 @@ export function useTeacherCompanion(options: Options) {
const base = projectId ? teacherTopicsPath(projectId) : '';
type LoadedCatalog = { enabled: boolean; selected?: TeacherCatalog['items'][number]; selectedId?: string };
let configFlight: Promise<LoadedCatalog> | null = null;
let catalogRevision: number | undefined;
let checkingCatalog = false;
const loadConfig = (): Promise<LoadedCatalog> => {
if (configFlight) return configFlight;
configFlight = (async () => {
const [config, catalog] = await Promise.all([teacherApi.config(), teacherApi.catalog()]);
if (!alive || scopeRef.current !== scope) return { enabled: false };
catalogRevision = catalog.revision;
const selection = (current: CompanionState) => {
const selectedId = current.selectedAgent?.teacher_id ?? current.topic?.definition.config_id
?? readLocal<string | undefined>(storageKey + ':selected-agent', undefined);
@@ -215,10 +218,7 @@ export function useTeacherCompanion(options: Options) {
}
// Delivered agents own one chat per project. Project observations
// have their own Main scheduler; this legacy timer must not create topics.
if (currentState.selectedAgent || currentState.agents.length) {
try { await loadConfig(); } catch { /* Keep last known delivered contacts. */ }
return;
}
if (currentState.selectedAgent || currentState.agents.length) return;
if (!projectId || current.sourceBusy || current.sourceArchived || !current.sourceId || (current.teacherOpen && current.teacherComposing)) return;
if (Date.now() - lastAttempt < TEACHER_CHECK_IN_INTERVAL_MS) return;
if (unreadInvitation(currentState)) return;
@@ -252,13 +252,25 @@ export function useTeacherCompanion(options: Options) {
}
};
const timer = window.setInterval(() => { void check(); }, 15_000);
const onVisible = () => { void check(); };
// Catalog freshness is independent of chat/check-in state and never dispatches a model.
const refreshCatalog = async () => {
if (!alive || checkingCatalog || document.visibilityState !== 'visible') return;
checkingCatalog = true;
try {
const current = await teacherApi.catalogRevision();
if (alive && scopeRef.current === scope && current.revision !== catalogRevision) await loadConfig();
} catch { /* Keep the last usable catalog; the next visible tick retries. */ }
finally { checkingCatalog = false; }
};
const catalogTimer = window.setInterval(() => { void refreshCatalog(); }, 5_000);
const onVisible = () => { void refreshCatalog(); void check(); };
window.addEventListener('focus', onVisible);
document.addEventListener('visibilitychange', onVisible);
return () => {
alive = false;
if (configLoader.current === loadConfig) configLoader.current = null;
window.clearInterval(timer);
window.clearInterval(catalogTimer);
window.removeEventListener('focus', onVisible);
document.removeEventListener('visibilitychange', onVisible);
};