接通偏好与文风库的接口与库页,并把采集间隔放进设置。
让用户能手写、提取入库,后续节点绑定才有内容可注入。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,10 +13,15 @@ export type AppSettings = {
|
||||
* 更早的消息仍保留,但去掉 contextTrace 正文。默认 5;0 表示不存痕迹。
|
||||
*/
|
||||
contextTraceKeepLatest?: number;
|
||||
/**
|
||||
* 游玩期每隔多少个用户回合尝试采集偏好候选。默认 10;0 = 关闭。
|
||||
*/
|
||||
preferenceCollectEveryTurns?: number;
|
||||
};
|
||||
|
||||
const FILE_NAME = "settings.json";
|
||||
const DEFAULT_CONTEXT_TRACE_KEEP = 5;
|
||||
const DEFAULT_PREF_COLLECT_EVERY = 10;
|
||||
|
||||
function settingsPath(): string {
|
||||
return path.join(getUserDataDir(), FILE_NAME);
|
||||
@@ -28,6 +33,7 @@ function defaultSettings(): AppSettings {
|
||||
activeProfileId: null,
|
||||
activePresetId: null,
|
||||
contextTraceKeepLatest: DEFAULT_CONTEXT_TRACE_KEEP,
|
||||
preferenceCollectEveryTurns: DEFAULT_PREF_COLLECT_EVERY,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +43,12 @@ export function normalizeContextTraceKeepLatest(raw: unknown): number {
|
||||
return Math.min(50, Math.floor(n));
|
||||
}
|
||||
|
||||
export function normalizePreferenceCollectEveryTurns(raw: unknown): number {
|
||||
const n = typeof raw === "number" ? raw : Number(raw);
|
||||
if (!Number.isFinite(n) || n < 0) return DEFAULT_PREF_COLLECT_EVERY;
|
||||
return Math.min(100, Math.floor(n));
|
||||
}
|
||||
|
||||
export function loadAppSettings(): AppSettings {
|
||||
ensureUserDataDirs();
|
||||
try {
|
||||
@@ -50,6 +62,9 @@ export function loadAppSettings(): AppSettings {
|
||||
contextTraceKeepLatest: normalizeContextTraceKeepLatest(
|
||||
parsed.contextTraceKeepLatest ?? DEFAULT_CONTEXT_TRACE_KEEP,
|
||||
),
|
||||
preferenceCollectEveryTurns: normalizePreferenceCollectEveryTurns(
|
||||
parsed.preferenceCollectEveryTurns ?? DEFAULT_PREF_COLLECT_EVERY,
|
||||
),
|
||||
};
|
||||
} catch {
|
||||
return defaultSettings();
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
ensureActiveProfileDefault,
|
||||
loadAppSettings,
|
||||
normalizeContextTraceKeepLatest,
|
||||
normalizePreferenceCollectEveryTurns,
|
||||
saveAppSettings,
|
||||
setActivePresetId,
|
||||
setActiveProfileId,
|
||||
@@ -36,6 +37,7 @@ import {
|
||||
} from "../preset/store.js";
|
||||
import type { GenerationParameters } from "../types/preset.js";
|
||||
import {
|
||||
createDefaultMainAgentLlm,
|
||||
createLlmForPreset,
|
||||
hasRealLlmConfig,
|
||||
} from "../runtime/llm-factory.js";
|
||||
@@ -48,6 +50,24 @@ import {
|
||||
setActivePersonaId,
|
||||
updatePersona,
|
||||
} from "../persona/store.js";
|
||||
import {
|
||||
createPreference,
|
||||
deletePreference,
|
||||
ensureStarterPreferences,
|
||||
extractPreferenceTurn,
|
||||
listPreferences,
|
||||
updatePreference,
|
||||
type PreferenceExtractCandidate,
|
||||
type PreferenceExtractMessage,
|
||||
} from "../preference/index.js";
|
||||
import {
|
||||
createStylePack,
|
||||
deleteStylePack,
|
||||
extractStylePackTurn,
|
||||
listStylePacks,
|
||||
updateStylePack,
|
||||
type StyleExtractMessage,
|
||||
} from "../style-pack/index.js";
|
||||
import { listDirectiveCatalog } from "../directives/expand.js";
|
||||
import { sessionManager } from "./session-manager.js";
|
||||
|
||||
@@ -104,6 +124,7 @@ export async function handleSettingsApi(
|
||||
activeProfileId?: string | null;
|
||||
activePresetId?: string | null;
|
||||
contextTraceKeepLatest?: number;
|
||||
preferenceCollectEveryTurns?: number;
|
||||
};
|
||||
const settings = loadAppSettings();
|
||||
if (body.activeProfileId !== undefined) {
|
||||
@@ -117,6 +138,11 @@ export async function handleSettingsApi(
|
||||
body.contextTraceKeepLatest,
|
||||
);
|
||||
}
|
||||
if (body.preferenceCollectEveryTurns !== undefined) {
|
||||
settings.preferenceCollectEveryTurns = normalizePreferenceCollectEveryTurns(
|
||||
body.preferenceCollectEveryTurns,
|
||||
);
|
||||
}
|
||||
saveAppSettings(settings);
|
||||
json(res, 200, { settings });
|
||||
return true;
|
||||
@@ -537,5 +563,182 @@ export async function handleSettingsApi(
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === "/api/preferences" && req.method === "GET") {
|
||||
ensureStarterPreferences();
|
||||
json(res, 200, {
|
||||
preferences: listPreferences(),
|
||||
preferenceCollectEveryTurns: normalizePreferenceCollectEveryTurns(
|
||||
loadAppSettings().preferenceCollectEveryTurns,
|
||||
),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathname === "/api/preferences" && req.method === "POST") {
|
||||
try {
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
content?: string;
|
||||
status?: "active" | "archived";
|
||||
};
|
||||
const entry = createPreference({
|
||||
content: body.content ?? "",
|
||||
status: body.status,
|
||||
});
|
||||
json(res, 201, { preference: entry, preferences: listPreferences() });
|
||||
} catch (e) {
|
||||
json(res, 400, {
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathname === "/api/preferences/extract" && req.method === "POST") {
|
||||
try {
|
||||
if (!hasRealLlmConfig()) {
|
||||
json(res, 400, { error: "未配置 API Key,无法提取偏好" });
|
||||
return true;
|
||||
}
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
messages?: PreferenceExtractMessage[];
|
||||
draftCandidates?: PreferenceExtractCandidate[];
|
||||
};
|
||||
const messages = Array.isArray(body.messages) ? body.messages : [];
|
||||
const draftCandidates = Array.isArray(body.draftCandidates)
|
||||
? body.draftCandidates
|
||||
: [];
|
||||
const llm = createDefaultMainAgentLlm();
|
||||
const result = await extractPreferenceTurn({
|
||||
llm,
|
||||
messages,
|
||||
draftCandidates,
|
||||
});
|
||||
json(res, 200, result);
|
||||
} catch (e) {
|
||||
json(res, 400, {
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const prefMatch = pathname.match(/^\/api\/preferences\/([^/]+)$/);
|
||||
if (prefMatch) {
|
||||
const id = decodeURIComponent(prefMatch[1]);
|
||||
if (req.method === "PUT") {
|
||||
try {
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
content?: string;
|
||||
status?: "active" | "archived";
|
||||
};
|
||||
const preference = updatePreference(id, body);
|
||||
json(res, 200, { preference, preferences: listPreferences() });
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
json(res, msg.includes("不存在") ? 404 : 400, { error: msg });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (req.method === "DELETE") {
|
||||
try {
|
||||
deletePreference(id);
|
||||
json(res, 200, { ok: true, preferences: listPreferences() });
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
json(res, msg.includes("不存在") ? 404 : 400, { error: msg });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === "/api/style-packs" && req.method === "GET") {
|
||||
json(res, 200, { stylePacks: listStylePacks() });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathname === "/api/style-packs" && req.method === "POST") {
|
||||
try {
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
name?: string;
|
||||
content?: string;
|
||||
samples?: string;
|
||||
status?: "active" | "archived";
|
||||
};
|
||||
const entry = createStylePack({
|
||||
name: body.name ?? "",
|
||||
content: body.content ?? "",
|
||||
samples: body.samples,
|
||||
status: body.status,
|
||||
});
|
||||
json(res, 201, { stylePack: entry, stylePacks: listStylePacks() });
|
||||
} catch (e) {
|
||||
json(res, 400, {
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathname === "/api/style-packs/extract" && req.method === "POST") {
|
||||
try {
|
||||
if (!hasRealLlmConfig()) {
|
||||
json(res, 400, { error: "未配置 API Key,无法提取文风" });
|
||||
return true;
|
||||
}
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
messages?: StyleExtractMessage[];
|
||||
samples?: string;
|
||||
draftName?: string;
|
||||
draftContent?: string;
|
||||
};
|
||||
const messages = Array.isArray(body.messages) ? body.messages : [];
|
||||
const llm = createDefaultMainAgentLlm();
|
||||
const result = await extractStylePackTurn({
|
||||
llm,
|
||||
messages,
|
||||
samples: body.samples,
|
||||
draftName: body.draftName,
|
||||
draftContent: body.draftContent,
|
||||
});
|
||||
json(res, 200, result);
|
||||
} catch (e) {
|
||||
json(res, 400, {
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const styleMatch = pathname.match(/^\/api\/style-packs\/([^/]+)$/);
|
||||
if (styleMatch) {
|
||||
const id = decodeURIComponent(styleMatch[1]);
|
||||
if (req.method === "PUT") {
|
||||
try {
|
||||
const body = JSON.parse(await readBody(req)) as {
|
||||
name?: string;
|
||||
content?: string;
|
||||
samples?: string | null;
|
||||
status?: "active" | "archived";
|
||||
};
|
||||
const stylePack = updateStylePack(id, body);
|
||||
json(res, 200, { stylePack, stylePacks: listStylePacks() });
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
json(res, msg.includes("不存在") ? 404 : 400, { error: msg });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (req.method === "DELETE") {
|
||||
try {
|
||||
deleteStylePack(id);
|
||||
json(res, 200, { ok: true, stylePacks: listStylePacks() });
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
json(res, msg.includes("不存在") ? 404 : 400, { error: msg });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user