227 lines
6.7 KiB
TypeScript
227 lines
6.7 KiB
TypeScript
import type { AgentThreadContext } from "../threads";
|
|
|
|
export const DEFAULT_LOCAL_SETTINGS: LocalSettings = {
|
|
notification: {
|
|
enabled: true,
|
|
},
|
|
display: {
|
|
streamSpeedMeterEnabled: true,
|
|
ragModeEnabled: false,
|
|
artifactDirectoryModeEnabled: false,
|
|
contextCompressionEnabled: false,
|
|
citationCleanupWords: [],
|
|
fontScale: 1,
|
|
},
|
|
context: {
|
|
model_name: undefined,
|
|
mode: undefined,
|
|
reasoning_effort: undefined,
|
|
writing_mode: false,
|
|
},
|
|
};
|
|
|
|
export const LOCAL_SETTINGS_KEY = "deerflow.local-settings";
|
|
export const THREAD_MODEL_KEY_PREFIX = "deerflow.thread-model.";
|
|
export const THREAD_RAG_MODE_KEY_PREFIX = "deerflow.thread-rag-mode.";
|
|
export const LAST_AGENT_ID_KEY = "deerflow.last-agent-id";
|
|
|
|
function isBrowser(): boolean {
|
|
return typeof window !== "undefined";
|
|
}
|
|
|
|
export interface LocalSettings {
|
|
notification: {
|
|
enabled: boolean;
|
|
};
|
|
display: {
|
|
/**
|
|
* Whether to render a small token/s speed meter while a streaming
|
|
* AI reply is in flight. Default on; controlled by the user from
|
|
* the appearance settings page.
|
|
*/
|
|
streamSpeedMeterEnabled: boolean;
|
|
/**
|
|
* Global switch for skill result display adapters. When disabled, chat
|
|
* messages render tool results with the legacy generic search UI only.
|
|
*/
|
|
ragModeEnabled: boolean;
|
|
/**
|
|
* When enabled, the artifact panel scans every generated output and keeps
|
|
* a directory tree visible beside the selected file preview.
|
|
*/
|
|
artifactDirectoryModeEnabled: boolean;
|
|
/**
|
|
* Per-user switch for context compression (上下文压缩). When enabled, long
|
|
* conversations are summarized transiently before the model call to save
|
|
* tokens. Default off; toggled from the chat input box (below 参考文献).
|
|
*/
|
|
contextCompressionEnabled: boolean;
|
|
/**
|
|
* Admin-maintained words or short phrases removed from reference snippets
|
|
* and dialogs, one entry per line in the settings UI.
|
|
*/
|
|
citationCleanupWords: string[];
|
|
/**
|
|
* Global UI font scale multiplier applied to the root <html> font-size.
|
|
* Base is 16px (deep-blue UI spec §2.1.2 body text), so `1` === 16px.
|
|
* The user picks a size from the appearance settings page; because the
|
|
* app sizes text/spacing in rem, the whole UI scales proportionally.
|
|
*/
|
|
fontScale: number;
|
|
/**
|
|
* Per-user override for the skill / agent list display mode (card vs list).
|
|
* `undefined` = follow the admin-configured system default. Set when the
|
|
* user flips the card/list toggle on the skills or agents page.
|
|
*/
|
|
skillViewMode?: "card" | "list";
|
|
agentViewMode?: "card" | "list";
|
|
/**
|
|
* Layout mode: "full" shows the page sidebar + header; "compact" hides them
|
|
* for a cleaner work area. Defaults to "full" (undefined treated as "full").
|
|
*/
|
|
layoutMode?: "full" | "compact";
|
|
};
|
|
context: Omit<
|
|
AgentThreadContext,
|
|
| "thread_id"
|
|
| "is_plan_mode"
|
|
| "thinking_enabled"
|
|
| "subagent_enabled"
|
|
| "model_name"
|
|
| "reasoning_effort"
|
|
// Session-only sidebar switch (core/settings/knowledge-rag.ts) — must not
|
|
// round-trip through localStorage.
|
|
| "knowledge_rag_enabled"
|
|
> & {
|
|
model_name?: string | undefined;
|
|
mode: "flash" | "thinking" | "pro" | "ultra" | undefined;
|
|
reasoning_effort?: "minimal" | "low" | "medium" | "high";
|
|
};
|
|
}
|
|
|
|
function mergeLocalSettings(settings?: Partial<LocalSettings>): LocalSettings {
|
|
const context = {
|
|
...DEFAULT_LOCAL_SETTINGS.context,
|
|
...settings?.context,
|
|
};
|
|
// Drop the value persisted by the retired per-thread header toggle; the
|
|
// knowledge-RAG switch now lives in the sidebar and is session-only.
|
|
delete (context as Record<string, unknown>).knowledge_rag_enabled;
|
|
return {
|
|
...DEFAULT_LOCAL_SETTINGS,
|
|
context,
|
|
notification: {
|
|
...DEFAULT_LOCAL_SETTINGS.notification,
|
|
...settings?.notification,
|
|
},
|
|
display: {
|
|
...DEFAULT_LOCAL_SETTINGS.display,
|
|
...settings?.display,
|
|
},
|
|
};
|
|
}
|
|
|
|
function getThreadModelStorageKey(threadId: string): string {
|
|
return `${THREAD_MODEL_KEY_PREFIX}${threadId}`;
|
|
}
|
|
|
|
export function getThreadModelName(threadId: string): string | undefined {
|
|
if (!isBrowser()) {
|
|
return undefined;
|
|
}
|
|
return localStorage.getItem(getThreadModelStorageKey(threadId)) ?? undefined;
|
|
}
|
|
|
|
export function saveThreadModelName(
|
|
threadId: string,
|
|
modelName: string | undefined,
|
|
) {
|
|
if (!isBrowser()) {
|
|
return;
|
|
}
|
|
const key = getThreadModelStorageKey(threadId);
|
|
if (!modelName) {
|
|
localStorage.removeItem(key);
|
|
return;
|
|
}
|
|
localStorage.setItem(key, modelName);
|
|
}
|
|
|
|
function getThreadRagModeStorageKey(threadId: string): string {
|
|
return `${THREAD_RAG_MODE_KEY_PREFIX}${threadId}`;
|
|
}
|
|
|
|
export function isThreadRagModeLocked(threadId: string): boolean {
|
|
if (!isBrowser() || !threadId) {
|
|
return false;
|
|
}
|
|
return localStorage.getItem(getThreadRagModeStorageKey(threadId)) === "true";
|
|
}
|
|
|
|
export function lockThreadRagMode(threadId: string) {
|
|
if (!isBrowser() || !threadId) {
|
|
return;
|
|
}
|
|
localStorage.setItem(getThreadRagModeStorageKey(threadId), "true");
|
|
}
|
|
|
|
export function applyThreadModelOverride(
|
|
settings: LocalSettings,
|
|
threadModelName: string | undefined,
|
|
): LocalSettings {
|
|
if (!threadModelName) {
|
|
return settings;
|
|
}
|
|
return {
|
|
...settings,
|
|
context: {
|
|
...settings.context,
|
|
model_name: threadModelName,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The id of the agent the user last started a conversation with. New-chat
|
|
* landing screens pre-select this agent so the user doesn't have to re-pick it
|
|
* every time. Returns ``null`` when nothing was stored yet.
|
|
*/
|
|
export function getLastAgentId(): string | null {
|
|
if (!isBrowser()) {
|
|
return null;
|
|
}
|
|
return localStorage.getItem(LAST_AGENT_ID_KEY) || null;
|
|
}
|
|
|
|
export function saveLastAgentId(agentId: string | null | undefined) {
|
|
if (!isBrowser()) {
|
|
return;
|
|
}
|
|
if (!agentId) {
|
|
localStorage.removeItem(LAST_AGENT_ID_KEY);
|
|
return;
|
|
}
|
|
localStorage.setItem(LAST_AGENT_ID_KEY, agentId);
|
|
}
|
|
|
|
export function getLocalSettings(): LocalSettings {
|
|
if (!isBrowser()) {
|
|
return DEFAULT_LOCAL_SETTINGS;
|
|
}
|
|
const json = localStorage.getItem(LOCAL_SETTINGS_KEY);
|
|
try {
|
|
if (json) {
|
|
const settings = JSON.parse(json) as Partial<LocalSettings>;
|
|
return mergeLocalSettings(settings);
|
|
}
|
|
} catch {}
|
|
return DEFAULT_LOCAL_SETTINGS;
|
|
}
|
|
|
|
export function saveLocalSettings(settings: LocalSettings) {
|
|
if (!isBrowser()) {
|
|
return;
|
|
}
|
|
localStorage.setItem(LOCAL_SETTINGS_KEY, JSON.stringify(settings));
|
|
}
|