Merge teacher composer and compact prompt UI into main

This commit is contained in:
鲨鱼辣椒
2026-09-29 20:40:09 +08:00
62 changed files with 2327 additions and 158 deletions

View File

@@ -33,6 +33,7 @@ import { TeacherConversationStore } from './conversation-store';
import { readJsonFile } from '../coding-projects/atomic-json';
import { compileTeacherContext } from './context';
import { prepareTeacherModel } from './model-runner';
import { teacherModelCatalog, validateTeacherModelId } from './model-catalog';
import { prepareCloudTeacher } from './cloud-runner';
import { readTeacherSource } from './source-reader';
import { applyTeacherReply, teacherReplyInstructions } from './reply';
@@ -60,6 +61,7 @@ export interface TeacherServiceOptions {
prepareModel?: typeof prepareTeacherModel;
prepareCloud?: typeof prepareCloudTeacher;
catalog?: typeof teacherCatalog;
models?: typeof teacherModelCatalog;
readSource?(scope: TeacherScope): Promise<TeacherSourceContext>;
}
export class CodingTeacherService {
@@ -101,6 +103,9 @@ export class CodingTeacherService {
: null;
return { ...status, definition: published?.payload ?? null };
}
async models(runtime: 'local' | 'yuxi') {
return (this.options.models ?? teacherModelCatalog)(await this.account(), runtime);
}
async catalog() {
return (this.options.catalog ?? teacherCatalog)(await this.account());
}
@@ -421,6 +426,7 @@ export class CodingTeacherService {
checkIn?: { source: TeacherSourceContext; fingerprint: string }
): Promise<TeacherTopic> {
teacherTopicId(input.requestId);
const modelId = validateTeacherModelId(input.modelId);
const intent = input.intent === undefined ? 'question' : input.intent;
const structuredReply = intent !== 'check-in';
if (!['question', 'suggestions', 'guided-help', 'check-in'].includes(intent) || (intent === 'check-in' && !checkIn))
@@ -458,6 +464,7 @@ export class CodingTeacherService {
if (existing) {
if (
existing.text !== input.text ||
existing.modelId !== modelId ||
(scope.agentId && existing.projectId !== scope.projectId) ||
(existing.intent ?? 'question') !== intent ||
JSON.stringify(existing.references) !== JSON.stringify(refs) ||
@@ -485,6 +492,7 @@ export class CodingTeacherService {
const previous = topic.requests.at(-1);
const reuse = previous && !previous.origin && previous.projectId === scope.projectId
&& previous.sourceConversationId === input.sourceConversationId
&& previous.modelId === modelId
&& previous.presentation === 'reply-v1'
&& previous.teacherVersion === selected.version && previous.runtimeThreadId;
newConversationSegment = !reuse;
@@ -551,6 +559,15 @@ export class CodingTeacherService {
return structuredClone(ref);
});
const isCloud = topic.definition.runtime === 'yuxi';
if (modelId) {
const models = await (this.options.models ?? teacherModelCatalog)(account, isCloud ? 'yuxi' : 'local');
if (!models.supported || !models.models.some(item => item.id === modelId))
throw new TeacherError(422, 'teacher_model_unavailable', models.message ?? '这个模型暂不可用,请重新选择。');
this.assertAccount(account);
}
const modelDefinition = modelId
? { ...topic.definition, model: { model_id: modelId, reasoning_choice: { mode: 'default' as const } } }
: topic.definition;
if (!isCloud && !topic.definition.system_prompt.trim())
throw new TeacherError(422, 'teacher_definition_invalid', '请先配置智能体的系统提示词。');
const access = scope.projectId === 'preview' ? undefined : {
@@ -579,7 +596,7 @@ export class CodingTeacherService {
topic.revision++;
this.events.emit(key, structuredClone(topic));
})
: await (this.options.prepareModel ?? prepareTeacherModel)(account, topic.definition,
: await (this.options.prepareModel ?? prepareTeacherModel)(account, modelDefinition,
access,
{ finalOnly: structuredReply });
if (checkIn) this.assertAccount(account);
@@ -620,6 +637,7 @@ export class CodingTeacherService {
text: input.text,
references,
createdAt: new Date().toISOString(),
...(modelId ? { modelId } : {}),
sourceCursor: source.cursor,
sourceCapturedAt: source.capturedAt,
includedSourceMessageIds: compiled.includedSourceMessageIds,
@@ -677,7 +695,8 @@ export class CodingTeacherService {
current.response = '';
delete current.suggestedQuestions;
}
topic.updatedAt = new Date().toISOString();
current.finishedAt = new Date().toISOString();
topic.updatedAt = current.finishedAt;
topic.revision++;
try {
await store.save(topic, current.id);