Refactor assistant configuration and database seeding
- Update Makefile to include new database seed commands for assistants and credentials. - Refactor assistant model to use explicit fields instead of a config dictionary, improving data integrity and clarity. - Implement new seeding SQL script for assistants, ensuring dependencies on credentials are respected. - Modify backend routes and frontend components to accommodate the new assistant structure, including direct field access for prompt, API URL, and keys. - Enhance the AssistantPage component to handle the new data structure and streamline the save process for different assistant types.
This commit is contained in:
@@ -339,7 +339,7 @@ export function AssistantPage() {
|
||||
setForm({
|
||||
name: a.name,
|
||||
greeting: a.greeting,
|
||||
prompt: typeof a.config.prompt === "string" ? a.config.prompt : "",
|
||||
prompt: a.prompt,
|
||||
runtimeMode: a.runtimeMode,
|
||||
realtimeModel: a.realtimeCredentialId ?? "",
|
||||
model: a.llmCredentialId ?? "",
|
||||
@@ -464,70 +464,32 @@ export function AssistantPage() {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存提示词助手(新建 POST / 编辑 PUT)
|
||||
async function handleSavePrompt() {
|
||||
setSaving(true);
|
||||
setSaveError(null);
|
||||
const payload: AssistantUpsert = {
|
||||
name: form.name.trim(),
|
||||
// 平铺字段的空白 upsert,各类型按需覆盖(后端会按 type 清掉无关字段)
|
||||
function baseUpsert(over: Partial<AssistantUpsert>): AssistantUpsert {
|
||||
return {
|
||||
name: "",
|
||||
type: "prompt",
|
||||
runtimeMode: form.runtimeMode,
|
||||
greeting: form.greeting,
|
||||
enableInterrupt: form.enableInterrupt,
|
||||
llmCredentialId: form.model || null,
|
||||
asrCredentialId: form.asr || null,
|
||||
ttsCredentialId: form.voice || null,
|
||||
realtimeCredentialId: form.realtimeModel || null,
|
||||
knowledgeBaseId: form.knowledgeBase || null,
|
||||
config: { prompt: form.prompt },
|
||||
};
|
||||
try {
|
||||
if (editingId) {
|
||||
await assistantsApi.update(editingId, payload);
|
||||
} else {
|
||||
await assistantsApi.create(payload);
|
||||
}
|
||||
await loadAssistants();
|
||||
setView("list");
|
||||
} catch (error) {
|
||||
setSaveError(error instanceof Error ? error.message : "保存失败");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
// config 里取字符串字段(后端回传是 Record<string, unknown>)
|
||||
const cfgStr = (a: Assistant, key: string) =>
|
||||
typeof a.config[key] === "string" ? (a.config[key] as string) : "";
|
||||
|
||||
// ---- Dify ----
|
||||
function fillDifyForm(a: Assistant) {
|
||||
setDifyForm({
|
||||
name: a.name,
|
||||
apiUrl: cfgStr(a, "apiUrl"),
|
||||
apiKey: cfgStr(a, "apiKey"), // 编辑时为打码值,不改则原样回传(后端哨兵保留旧 key)
|
||||
asr: a.asrCredentialId ?? "",
|
||||
voice: a.ttsCredentialId ?? "",
|
||||
enableInterrupt: a.enableInterrupt,
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSaveDify() {
|
||||
setSaving(true);
|
||||
setSaveError(null);
|
||||
const payload: AssistantUpsert = {
|
||||
name: difyForm.name.trim(),
|
||||
type: "dify",
|
||||
runtimeMode: "pipeline",
|
||||
greeting: "",
|
||||
enableInterrupt: difyForm.enableInterrupt,
|
||||
llmCredentialId: null, // LLM 由 Dify 应用提供
|
||||
asrCredentialId: difyForm.asr || null,
|
||||
ttsCredentialId: difyForm.voice || null,
|
||||
enableInterrupt: true,
|
||||
llmCredentialId: null,
|
||||
asrCredentialId: null,
|
||||
ttsCredentialId: null,
|
||||
realtimeCredentialId: null,
|
||||
knowledgeBaseId: null,
|
||||
config: { apiUrl: difyForm.apiUrl, apiKey: difyForm.apiKey },
|
||||
prompt: "",
|
||||
apiUrl: "",
|
||||
apiKey: "",
|
||||
appId: "",
|
||||
graph: {},
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
// 统一的保存:新建 POST / 编辑 PUT
|
||||
async function save(payload: AssistantUpsert) {
|
||||
setSaving(true);
|
||||
setSaveError(null);
|
||||
try {
|
||||
if (editingId) await assistantsApi.update(editingId, payload);
|
||||
else await assistantsApi.create(payload);
|
||||
@@ -540,49 +502,76 @@ export function AssistantPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleSavePrompt() {
|
||||
void save(
|
||||
baseUpsert({
|
||||
name: form.name.trim(),
|
||||
type: "prompt",
|
||||
runtimeMode: form.runtimeMode,
|
||||
greeting: form.greeting,
|
||||
enableInterrupt: form.enableInterrupt,
|
||||
llmCredentialId: form.model || null,
|
||||
asrCredentialId: form.asr || null,
|
||||
ttsCredentialId: form.voice || null,
|
||||
realtimeCredentialId: form.realtimeModel || null,
|
||||
knowledgeBaseId: form.knowledgeBase || null,
|
||||
prompt: form.prompt,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Dify ----
|
||||
function fillDifyForm(a: Assistant) {
|
||||
setDifyForm({
|
||||
name: a.name,
|
||||
apiUrl: a.apiUrl,
|
||||
apiKey: a.apiKey, // 编辑时为打码值,不改则原样回传(后端哨兵保留旧 key)
|
||||
asr: a.asrCredentialId ?? "",
|
||||
voice: a.ttsCredentialId ?? "",
|
||||
enableInterrupt: a.enableInterrupt,
|
||||
});
|
||||
}
|
||||
|
||||
function handleSaveDify() {
|
||||
void save(
|
||||
baseUpsert({
|
||||
name: difyForm.name.trim(),
|
||||
type: "dify",
|
||||
enableInterrupt: difyForm.enableInterrupt,
|
||||
asrCredentialId: difyForm.asr || null,
|
||||
ttsCredentialId: difyForm.voice || null,
|
||||
apiUrl: difyForm.apiUrl,
|
||||
apiKey: difyForm.apiKey,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// ---- FastGPT ----
|
||||
function fillFastGptForm(a: Assistant) {
|
||||
setFastGptForm({
|
||||
name: a.name,
|
||||
appId: cfgStr(a, "appId"),
|
||||
apiUrl: cfgStr(a, "apiUrl"),
|
||||
apiKey: cfgStr(a, "apiKey"),
|
||||
appId: a.appId,
|
||||
apiUrl: a.apiUrl,
|
||||
apiKey: a.apiKey,
|
||||
asr: a.asrCredentialId ?? "",
|
||||
voice: a.ttsCredentialId ?? "",
|
||||
enableInterrupt: a.enableInterrupt,
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSaveFastGpt() {
|
||||
setSaving(true);
|
||||
setSaveError(null);
|
||||
const payload: AssistantUpsert = {
|
||||
name: fastGptForm.name.trim(),
|
||||
type: "fastgpt",
|
||||
runtimeMode: "pipeline",
|
||||
greeting: "",
|
||||
enableInterrupt: fastGptForm.enableInterrupt,
|
||||
llmCredentialId: null,
|
||||
asrCredentialId: fastGptForm.asr || null,
|
||||
ttsCredentialId: fastGptForm.voice || null,
|
||||
realtimeCredentialId: null,
|
||||
knowledgeBaseId: null,
|
||||
config: {
|
||||
function handleSaveFastGpt() {
|
||||
void save(
|
||||
baseUpsert({
|
||||
name: fastGptForm.name.trim(),
|
||||
type: "fastgpt",
|
||||
enableInterrupt: fastGptForm.enableInterrupt,
|
||||
asrCredentialId: fastGptForm.asr || null,
|
||||
ttsCredentialId: fastGptForm.voice || null,
|
||||
appId: fastGptForm.appId,
|
||||
apiUrl: fastGptForm.apiUrl,
|
||||
apiKey: fastGptForm.apiKey,
|
||||
},
|
||||
};
|
||||
try {
|
||||
if (editingId) await assistantsApi.update(editingId, payload);
|
||||
else await assistantsApi.create(payload);
|
||||
await loadAssistants();
|
||||
setView("list");
|
||||
} catch (error) {
|
||||
setSaveError(error instanceof Error ? error.message : "保存失败");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const listItems: AssistantListItem[] = assistants.map((a) => ({
|
||||
|
||||
@@ -82,7 +82,7 @@ export type AssistantType =
|
||||
| "opencode";
|
||||
export type RuntimeMode = "pipeline" | "realtime";
|
||||
|
||||
/** 后端 AssistantOut。config 形态随 type 变,故用宽松 record */
|
||||
/** 后端 AssistantOut(宽表 STI:瘦字段平铺,workflow 用 graph)。apiKey 读时打码 */
|
||||
export type Assistant = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -95,23 +95,15 @@ export type Assistant = {
|
||||
ttsCredentialId: string | null;
|
||||
realtimeCredentialId: string | null;
|
||||
knowledgeBaseId: string | null;
|
||||
config: Record<string, unknown>;
|
||||
prompt: string;
|
||||
apiUrl: string;
|
||||
apiKey: string;
|
||||
appId: string;
|
||||
graph: Record<string, unknown>;
|
||||
updatedAt?: string | null;
|
||||
};
|
||||
|
||||
export type AssistantUpsert = {
|
||||
name: string;
|
||||
type: AssistantType;
|
||||
runtimeMode: RuntimeMode;
|
||||
greeting: string;
|
||||
enableInterrupt: boolean;
|
||||
llmCredentialId: string | null;
|
||||
asrCredentialId: string | null;
|
||||
ttsCredentialId: string | null;
|
||||
realtimeCredentialId: string | null;
|
||||
knowledgeBaseId: string | null;
|
||||
config: Record<string, unknown>;
|
||||
};
|
||||
export type AssistantUpsert = Omit<Assistant, "id" | "updatedAt">;
|
||||
|
||||
export const assistantsApi = {
|
||||
list: () => request<Assistant[]>("/api/assistants"),
|
||||
|
||||
Reference in New Issue
Block a user