| import type { AppSettings, ApiMode, SavedModelCredential } from "./types"; |
|
|
| const SETTINGS_KEY = "task-polisher.settings.v3"; |
| const LEGACY_SETTINGS_KEY = "task-polisher.settings.v2"; |
|
|
| export const MODEL_OPTIONS = [ |
| "deepseek-v4-pro", |
| "gpt-5.4", |
| "gpt-5.3-codex", |
| "gpt-5", |
| "gpt-5-codex", |
| "z-ai/glm-5.1", |
| "custom", |
| ]; |
|
|
| export const DEFAULT_SETTINGS: AppSettings = { |
| ruleProfile: "echo-05061347", |
| polishTransport: "browser", |
| analysisTransport: "backend", |
| apiMode: "chat", |
| baseURL: "https://api.deepseek.com", |
| apiKey: "", |
| savedModelCredentials: [], |
| selectedModelCredentialId: "", |
| modelPreset: "deepseek-v4-pro", |
| customModel: "", |
| concurrency: 3, |
| temperature: 0.7, |
| topP: 1, |
| maxTokens: 32768, |
| enableThinking: true, |
| clearThinking: false, |
| }; |
|
|
| export function resolveModel(settings: AppSettings): string { |
| return settings.modelPreset === "custom" ? settings.customModel.trim() : settings.modelPreset; |
| } |
|
|
| export function defaultBaseURLForMode(_apiMode: ApiMode): string { |
| return "https://api.deepseek.com"; |
| } |
|
|
| export function applyDeepSeekPreset(settings: AppSettings): AppSettings { |
| return sanitizeSettings({ |
| ...settings, |
| apiMode: "chat", |
| baseURL: "https://api.deepseek.com", |
| selectedModelCredentialId: "", |
| modelPreset: "deepseek-v4-pro", |
| customModel: "", |
| temperature: 0.7, |
| topP: 1, |
| maxTokens: 32768, |
| enableThinking: true, |
| clearThinking: false, |
| }); |
| } |
|
|
| export function loadSettings(): AppSettings { |
| const stored = readJson(SETTINGS_KEY) ?? readJson(LEGACY_SETTINGS_KEY) ?? {}; |
| return sanitizeSettings({ ...DEFAULT_SETTINGS, ...stored }); |
| } |
|
|
| export function saveSettings(settings: AppSettings): AppSettings { |
| const sanitized = sanitizeSettings(settings); |
| window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(sanitized)); |
| return sanitized; |
| } |
|
|
| export function sanitizeSettings(raw: Partial<AppSettings>): AppSettings { |
| const apiMode = raw.apiMode === "responses" ? "responses" : "chat"; |
| const modelPreset = MODEL_OPTIONS.includes(String(raw.modelPreset)) ? String(raw.modelPreset) : DEFAULT_SETTINGS.modelPreset; |
| const customModel = modelPreset === "custom" ? String(raw.customModel || "").trim() : ""; |
| const baseURL = String(raw.baseURL || DEFAULT_SETTINGS.baseURL).trim() || defaultBaseURLForMode(apiMode); |
| const apiKey = String(raw.apiKey || "").trim(); |
| const savedModelCredentials = sanitizeSavedModelCredentials(raw.savedModelCredentials); |
| const selectedModelCredentialId = savedModelCredentials.some((item) => item.id === raw.selectedModelCredentialId) |
| ? String(raw.selectedModelCredentialId) |
| : ""; |
|
|
| return { |
| ruleProfile: String(raw.ruleProfile || DEFAULT_SETTINGS.ruleProfile), |
| polishTransport: "browser", |
| analysisTransport: "backend", |
| apiMode, |
| baseURL, |
| apiKey, |
| savedModelCredentials, |
| selectedModelCredentialId, |
| modelPreset, |
| customModel, |
| concurrency: clampInteger(raw.concurrency, 1, 8, DEFAULT_SETTINGS.concurrency), |
| temperature: clampNumber(raw.temperature, 0, 2, DEFAULT_SETTINGS.temperature), |
| topP: clampNumber(raw.topP, 0, 1, DEFAULT_SETTINGS.topP), |
| maxTokens: clampInteger(raw.maxTokens, 1, 131072, DEFAULT_SETTINGS.maxTokens), |
| enableThinking: raw.enableThinking !== false, |
| clearThinking: Boolean(raw.clearThinking), |
| }; |
| } |
|
|
| function sanitizeSavedModelCredentials(value: unknown): SavedModelCredential[] { |
| if (!Array.isArray(value)) return []; |
| const seen = new Set<string>(); |
| return value |
| .map((item, index) => { |
| const raw = item as Partial<SavedModelCredential>; |
| const baseURL = String(raw?.baseURL || "").trim(); |
| const apiKey = String(raw?.apiKey || "").trim(); |
| if (!baseURL || !apiKey) return null; |
| const id = String(raw?.id || "").trim() || makeCredentialId(baseURL, index); |
| if (seen.has(id)) return null; |
| seen.add(id); |
| return { |
| id, |
| name: String(raw?.name || "").trim() || baseURL, |
| baseURL, |
| apiKey, |
| }; |
| }) |
| .filter((item): item is SavedModelCredential => Boolean(item)); |
| } |
|
|
| function makeCredentialId(baseURL: string, index: number): string { |
| return `credential-${index}-${baseURL.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "model"}`; |
| } |
|
|
| function clampNumber(value: unknown, min: number, max: number, fallback: number): number { |
| const parsed = Number(value); |
| if (!Number.isFinite(parsed)) return fallback; |
| return Math.min(max, Math.max(min, parsed)); |
| } |
|
|
| function clampInteger(value: unknown, min: number, max: number, fallback: number): number { |
| return Math.round(clampNumber(value, min, max, fallback)); |
| } |
|
|
| function readJson(key: string): Record<string, unknown> | null { |
| try { |
| const raw = window.localStorage.getItem(key); |
| return raw ? JSON.parse(raw) : null; |
| } catch { |
| return null; |
| } |
| } |
|
|