feat(teacher): 连接 Yuxi 老师并提供项目与会话只读工具
This commit is contained in:
@@ -17,12 +17,14 @@ import {
|
||||
teacherAvailability,
|
||||
teacherVersion,
|
||||
teacherPreview,
|
||||
teacherCatalog,
|
||||
TeacherError,
|
||||
type TeacherAccount,
|
||||
} from './config-client';
|
||||
import { TeacherTopicStore, teacherTopicId } from './store';
|
||||
import { compileTeacherContext } from './context';
|
||||
import { prepareTeacherModel } from './model-runner';
|
||||
import { prepareCloudTeacher } from './cloud-runner';
|
||||
import { readTeacherSource } from './source-reader';
|
||||
import { subscribeWorksSquareSession } from '../services/works-square-session';
|
||||
|
||||
@@ -44,6 +46,8 @@ export interface TeacherServiceOptions {
|
||||
version?: typeof teacherVersion;
|
||||
preview?: typeof teacherPreview;
|
||||
prepareModel?: typeof prepareTeacherModel;
|
||||
prepareCloud?: typeof prepareCloudTeacher;
|
||||
catalog?: typeof teacherCatalog;
|
||||
readSource?(scope: TeacherScope): Promise<TeacherSourceContext>;
|
||||
}
|
||||
export class CodingTeacherService {
|
||||
@@ -84,6 +88,9 @@ export class CodingTeacherService {
|
||||
: null;
|
||||
return { ...status, definition: published?.payload ?? null };
|
||||
}
|
||||
async catalog() {
|
||||
return (this.options.catalog ?? teacherCatalog)(await this.account());
|
||||
}
|
||||
private async scopedStore(
|
||||
account: TeacherAccount,
|
||||
scope: TeacherScope
|
||||
@@ -132,7 +139,8 @@ export class CodingTeacherService {
|
||||
async create(
|
||||
scope: TeacherScope,
|
||||
draftRevision?: number,
|
||||
sampleContext = ''
|
||||
sampleContext = '',
|
||||
teacherVersionNumber?: number,
|
||||
): Promise<TeacherTopic> {
|
||||
const account = await this.account();
|
||||
const store = await this.scopedStore(account, scope);
|
||||
@@ -148,6 +156,14 @@ export class CodingTeacherService {
|
||||
definition = (await (this.options.preview ?? teacherPreview)(account, draftRevision!))
|
||||
.payload;
|
||||
version = 0;
|
||||
} else if (teacherVersionNumber !== undefined) {
|
||||
if (!Number.isSafeInteger(teacherVersionNumber) || teacherVersionNumber < 1)
|
||||
throw new TeacherError(422, 'teacher_version_invalid', '老师版本无效。');
|
||||
const catalog = await (this.options.catalog ?? teacherCatalog)(account);
|
||||
const selected = catalog.items.find(item => item.version === teacherVersionNumber);
|
||||
if (!selected) throw new TeacherError(409, 'teacher_disabled', '该老师暂未开放,请刷新后选择。');
|
||||
definition = selected.definition;
|
||||
version = selected.version;
|
||||
} else {
|
||||
const status = await (this.options.availability ?? teacherAvailability)(account);
|
||||
if (!status.enabled || !status.published_version)
|
||||
@@ -236,7 +252,11 @@ export class CodingTeacherService {
|
||||
throw new TeacherError(409, 'teacher_topic_busy', '请等待当前回复完成,或先停止。');
|
||||
if (topic.draftRevision) {
|
||||
await (this.options.preview ?? teacherPreview)(account, topic.draftRevision);
|
||||
} else {
|
||||
} else if (topic.definition.runtime !== 'yuxi' && topic.definition.config_id) {
|
||||
const catalog = await (this.options.catalog ?? teacherCatalog)(account);
|
||||
if (!catalog.items.some(item => item.teacher_id === topic.definition.config_id))
|
||||
throw new TeacherError(409, 'teacher_disabled', '老师已停用,历史仍可查看。');
|
||||
} else if (topic.definition.runtime !== 'yuxi') {
|
||||
const available = await (this.options.availability ?? teacherAvailability)(account);
|
||||
if (!available.enabled)
|
||||
throw new TeacherError(409, 'teacher_disabled', '老师已停用,历史仍可查看。');
|
||||
@@ -272,22 +292,31 @@ export class CodingTeacherService {
|
||||
);
|
||||
return structuredClone(ref);
|
||||
});
|
||||
if (!topic.definition.system_prompt.trim())
|
||||
const isCloud = topic.definition.runtime === 'yuxi';
|
||||
if (!isCloud && !topic.definition.system_prompt.trim())
|
||||
throw new TeacherError(422, 'teacher_definition_invalid', '请先配置老师的系统提示词。');
|
||||
const model = await (this.options.prepareModel ?? prepareTeacherModel)(
|
||||
account,
|
||||
topic.definition,
|
||||
scope.projectId === 'preview' ? undefined : {
|
||||
const access = scope.projectId === 'preview' ? undefined : {
|
||||
projectPath: (await this.options.projects.getProject(scope.projectId)).path,
|
||||
source,
|
||||
history: topic.requests,
|
||||
assertCurrent: () => this.assertAccount(account),
|
||||
}
|
||||
);
|
||||
};
|
||||
const model = isCloud && access
|
||||
? (this.options.prepareCloud ?? prepareCloudTeacher)(account, topic, input.requestId, access,
|
||||
(progress) => {
|
||||
const current = topic.requests.at(-1)!;
|
||||
if (current.progress === progress) return;
|
||||
current.progress = progress; topic.revision++;
|
||||
this.events.emit(key, structuredClone(topic));
|
||||
}, async cloudRequestId => {
|
||||
topic.requests.at(-1)!.cloudRequestId = cloudRequestId;
|
||||
await store.save(topic);
|
||||
})
|
||||
: await (this.options.prepareModel ?? prepareTeacherModel)(account, topic.definition, access);
|
||||
const compiled = compileTeacherContext(
|
||||
topic.definition,
|
||||
source,
|
||||
topic.requests,
|
||||
isCloud ? [] : topic.requests,
|
||||
input.text,
|
||||
references,
|
||||
model.inputLimit,
|
||||
|
||||
Reference in New Issue
Block a user