499 lines
14 KiB
TypeScript
499 lines
14 KiB
TypeScript
// @ts-nocheck
|
|
|
|
|
|
export const CONFIG = {
|
|
|
|
ZHONGTAI_BASE: 'http://127.0.0.1:8088',
|
|
ZHONGTAI_METRICS_PATH: '/metrics',
|
|
ZHONGTAI_PROXY_PATH: '/proxy/anthropic-messages',
|
|
ZHONGTAI_OMLX_PROXY_PATH: '/proxy/omlx-chat',
|
|
|
|
|
|
MCP_BASE: 'http://127.0.0.1:7777',
|
|
MCP_PATH: '/mcp',
|
|
HTTP_PATH_PREFIX: '/api/',
|
|
|
|
|
|
OMLX_BASE: 'http://127.0.0.1:8000',
|
|
OMLX_CHAT_PATH: '/v1/chat/completions',
|
|
|
|
|
|
SPARKLE_BASE: 'http://127.0.0.1:8317',
|
|
SPARKLE_CHAT_PATH: '/v1/chat/completions',
|
|
|
|
|
|
MAX_TURNS: 8,
|
|
MAX_SAME_ERROR: 4,
|
|
|
|
MAX_REPORT_ROUNDS: 10,
|
|
|
|
REPORT_MAX_TOKENS: 1800,
|
|
|
|
|
|
METRICS_POLL_MS: 1500,
|
|
|
|
|
|
STREAM_RAF: true,
|
|
|
|
|
|
TOKEN_STORAGE_KEY: 'ccsparkle_agent_token',
|
|
MODE_STORAGE_KEY: 'ccsparkle_agent_mode',
|
|
MODEL_SOURCE_KEY: 'ccsparkle_model_source',
|
|
DEFAULT_SOURCE_KEY: 'ccsparkle_default_source',
|
|
OLLAMA_CFG_KEY: 'ccsparkle_ollama_cfg',
|
|
ANTHROPIC_CFG_KEY: 'ccsparkle_anthropic_cfg',
|
|
OMLX_CFG_KEY: 'ccsparkle_omlx_cfg',
|
|
SPARKLE_CFG_KEY: 'ccsparkle_sparkle_cfg',
|
|
HISTORY_STORAGE_KEY: 'ccsparkle_history',
|
|
CONTEXT_THRESHOLD_KEY: 'ccsparkle_context_threshold',
|
|
THINKING_ENABLED_KEY: 'ccsparkle_thinking_enabled',
|
|
CROSS_MODULE_ENABLED_KEY: 'ccsparkle_cross_module_enabled',
|
|
};
|
|
|
|
export function zhongtaiMetricsUrl() {
|
|
return `${CONFIG.ZHONGTAI_BASE}${CONFIG.ZHONGTAI_METRICS_PATH}`;
|
|
}
|
|
|
|
export function zhongtaiProxyUrl() {
|
|
return `${CONFIG.ZHONGTAI_BASE}${CONFIG.ZHONGTAI_PROXY_PATH}`;
|
|
}
|
|
|
|
export function zhongtaiOmlxProxyUrl() {
|
|
return `${CONFIG.ZHONGTAI_BASE}${CONFIG.ZHONGTAI_OMLX_PROXY_PATH}`;
|
|
}
|
|
|
|
export const DEFAULT_OLLAMA = {
|
|
baseUrl: 'http://127.0.0.1:11434',
|
|
model: '',
|
|
};
|
|
|
|
export const DEFAULT_ANTHROPIC = {
|
|
baseUrl: '',
|
|
authToken: '',
|
|
|
|
protocol: 'anthropic',
|
|
|
|
preset: 'custom',
|
|
fastModel: '',
|
|
standardModel: '',
|
|
proModel: '',
|
|
currentTier: 'fast',
|
|
apiVersion: '2023-06-01',
|
|
maxTokens: 4096,
|
|
|
|
|
|
presets: {},
|
|
};
|
|
|
|
export const CLOUD_API_PRESETS = [
|
|
{
|
|
id: 'zhipu_coding_anthropic',
|
|
label: '智谱 Coding · Messages',
|
|
protocol: 'anthropic',
|
|
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
hint: '智谱 Coding Plan 套餐额度,Messages API',
|
|
},
|
|
{
|
|
id: 'zhipu_coding_openai',
|
|
label: '智谱 Coding · Chat',
|
|
protocol: 'openai',
|
|
baseUrl: 'https://open.bigmodel.cn/api/coding/paas/v4',
|
|
hint: '智谱 Coding Plan 套餐额度,Chat Completions',
|
|
},
|
|
{
|
|
id: 'zhipu_balance',
|
|
label: '智谱 · 账号余额',
|
|
protocol: 'openai',
|
|
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
hint: '智谱独立计费,不消耗套餐',
|
|
},
|
|
{
|
|
id: 'bailian_anthropic',
|
|
label: '百炼 · Anthropic',
|
|
protocol: 'anthropic',
|
|
baseUrl: 'https://dashscope.aliyuncs.com/apps/anthropic',
|
|
hint: '阿里云百炼(北京),Messages API;Token 填 DashScope API Key',
|
|
models: {
|
|
fastModel: 'qwen-flash',
|
|
standardModel: 'qwen3.7-plus',
|
|
proModel: 'qwen3.7-max',
|
|
currentTier: 'pro',
|
|
},
|
|
},
|
|
{
|
|
id: 'bailian_openai',
|
|
label: '百炼 · OpenAI',
|
|
protocol: 'openai',
|
|
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
hint: '阿里云百炼(北京),Chat Completions;Token 填 DashScope API Key',
|
|
models: {
|
|
fastModel: 'qwen-flash',
|
|
standardModel: 'qwen3.7-plus',
|
|
proModel: 'qwen3.7-max',
|
|
currentTier: 'pro',
|
|
},
|
|
},
|
|
{
|
|
id: 'deepseek_official',
|
|
label: 'DeepSeek · 官方',
|
|
protocol: 'openai',
|
|
baseUrl: 'https://api.deepseek.com/v1',
|
|
hint: 'DeepSeek 官方,OpenAI 兼容;Token 填 DeepSeek API Key',
|
|
models: {
|
|
fastModel: 'deepseek-v4-flash',
|
|
standardModel: '',
|
|
proModel: 'deepseek-v4-pro',
|
|
currentTier: 'pro',
|
|
},
|
|
},
|
|
{
|
|
id: 'moonshot_official',
|
|
label: 'Moonshot Kimi · 官方按量',
|
|
protocol: 'openai',
|
|
baseUrl: 'https://api.moonshot.cn/v1',
|
|
hint: 'Moonshot 官方按量计费,OpenAI 兼容;Token 填 Moonshot API Key',
|
|
models: {
|
|
fastModel: 'moonshot-v1-8k',
|
|
standardModel: 'moonshot-v1-32k',
|
|
proModel: 'moonshot-v1-128k',
|
|
currentTier: 'pro',
|
|
},
|
|
},
|
|
{
|
|
id: 'kimi_coding_plan',
|
|
label: 'Kimi Code Plan · 套餐',
|
|
protocol: 'openai',
|
|
baseUrl: 'https://api.kimi.com/coding/v1',
|
|
hint: 'Kimi Code Plan 套餐(Andante/Moderato/Allegretto/Allegro),OpenAI 兼容;Token 填 Kimi 平台 API Key',
|
|
models: {
|
|
fastModel: 'kimi-k3',
|
|
standardModel: 'kimi-k3',
|
|
proModel: 'kimi-k3',
|
|
currentTier: 'pro',
|
|
},
|
|
},
|
|
{
|
|
id: 'qianfan_openai',
|
|
label: '百度千帆 · OpenAI',
|
|
protocol: 'openai',
|
|
baseUrl: 'https://qianfan.baidubce.com/v2/compatible',
|
|
hint: '百度千帆(OpenAI 兼容模式);Token 塺千帆 API Key(用 Bearer 鉴权)',
|
|
models: {
|
|
fastModel: 'ernie-speed-8k',
|
|
standardModel: 'ernie-4.0-8k-latest',
|
|
proModel: 'ernie-4.5-8k-latest',
|
|
currentTier: 'pro',
|
|
},
|
|
},
|
|
{
|
|
id: 'custom',
|
|
label: '自定义',
|
|
protocol: null,
|
|
baseUrl: null,
|
|
hint: '自行填写 Base URL 与协议',
|
|
},
|
|
];
|
|
|
|
export const DEFAULT_OMLX = {
|
|
baseUrl: 'http://127.0.0.1:8000',
|
|
apiKey: '',
|
|
model: '',
|
|
};
|
|
|
|
export const DEFAULT_SPARKLE = {
|
|
baseUrl: 'http://127.0.0.1:8317',
|
|
apiKey: '',
|
|
model: '',
|
|
};
|
|
|
|
export function mcpUrl() {
|
|
return `${CONFIG.MCP_BASE}${CONFIG.MCP_PATH}`;
|
|
}
|
|
|
|
export function httpUrl(toolName) {
|
|
return `${CONFIG.MCP_BASE}${CONFIG.HTTP_PATH_PREFIX}${toolName}`;
|
|
}
|
|
|
|
export function loadToken() {
|
|
const params = new URLSearchParams(location.search);
|
|
const fromUrl = params.get('token');
|
|
if (fromUrl) {
|
|
localStorage.setItem(CONFIG.TOKEN_STORAGE_KEY, fromUrl);
|
|
return fromUrl;
|
|
}
|
|
const stored = localStorage.getItem(CONFIG.TOKEN_STORAGE_KEY);
|
|
if (stored) return stored;
|
|
return '';
|
|
}
|
|
|
|
export function loadMode() {
|
|
return localStorage.getItem(CONFIG.MODE_STORAGE_KEY) || 'mcp';
|
|
}
|
|
|
|
export function saveMode(mode) {
|
|
localStorage.setItem(CONFIG.MODE_STORAGE_KEY, mode);
|
|
}
|
|
|
|
export function loadModelSource() {
|
|
|
|
const last = localStorage.getItem(CONFIG.MODEL_SOURCE_KEY);
|
|
if (last) return last;
|
|
return localStorage.getItem(CONFIG.DEFAULT_SOURCE_KEY) || 'ollama';
|
|
}
|
|
|
|
export function saveModelSource(source) {
|
|
localStorage.setItem(CONFIG.MODEL_SOURCE_KEY, source);
|
|
}
|
|
|
|
export function loadDefaultSource() {
|
|
return localStorage.getItem(CONFIG.DEFAULT_SOURCE_KEY) || 'ollama';
|
|
}
|
|
|
|
export function saveDefaultSource(source) {
|
|
localStorage.setItem(CONFIG.DEFAULT_SOURCE_KEY, source);
|
|
|
|
localStorage.setItem(CONFIG.MODEL_SOURCE_KEY, source);
|
|
}
|
|
|
|
export function loadOllamaConfig() {
|
|
try {
|
|
const s = localStorage.getItem(CONFIG.OLLAMA_CFG_KEY);
|
|
if (s) {
|
|
const cfg = { ...DEFAULT_OLLAMA, ...JSON.parse(s) };
|
|
|
|
if (!String(cfg.baseUrl || '').trim()) cfg.baseUrl = DEFAULT_OLLAMA.baseUrl;
|
|
return cfg;
|
|
}
|
|
} catch (e) {}
|
|
return { ...DEFAULT_OLLAMA };
|
|
}
|
|
|
|
export function saveOllamaConfig(cfg) {
|
|
localStorage.setItem(CONFIG.OLLAMA_CFG_KEY, JSON.stringify(cfg));
|
|
}
|
|
|
|
export function loadAnthropicConfig() {
|
|
try {
|
|
const s = localStorage.getItem(CONFIG.ANTHROPIC_CFG_KEY);
|
|
if (s) {
|
|
const raw = JSON.parse(s);
|
|
const cfg = { ...DEFAULT_ANTHROPIC, ...raw };
|
|
|
|
if (raw.protocol == null) {
|
|
const base = (cfg.baseUrl || '').toLowerCase();
|
|
if (/\/paas\/v4|\/coding\/paas/.test(base)) {
|
|
cfg.protocol = 'openai';
|
|
}
|
|
}
|
|
return cfg;
|
|
}
|
|
} catch (e) {}
|
|
return { ...DEFAULT_ANTHROPIC };
|
|
}
|
|
|
|
export function saveAnthropicConfig(cfg) {
|
|
localStorage.setItem(CONFIG.ANTHROPIC_CFG_KEY, JSON.stringify(cfg));
|
|
}
|
|
|
|
export function loadOmlxConfig() {
|
|
try {
|
|
const s = localStorage.getItem(CONFIG.OMLX_CFG_KEY);
|
|
if (s) {
|
|
const cfg = { ...DEFAULT_OMLX, ...JSON.parse(s) };
|
|
if (!String(cfg.baseUrl || '').trim()) cfg.baseUrl = DEFAULT_OMLX.baseUrl;
|
|
return cfg;
|
|
}
|
|
} catch (e) {}
|
|
return { ...DEFAULT_OMLX };
|
|
}
|
|
|
|
export function saveOmlxConfig(cfg) {
|
|
localStorage.setItem(CONFIG.OMLX_CFG_KEY, JSON.stringify(cfg));
|
|
}
|
|
|
|
export function loadSparkleConfig() {
|
|
try {
|
|
const s = localStorage.getItem(CONFIG.SPARKLE_CFG_KEY);
|
|
if (s) {
|
|
const cfg = { ...DEFAULT_SPARKLE, ...JSON.parse(s) };
|
|
if (!String(cfg.baseUrl || '').trim()) cfg.baseUrl = DEFAULT_SPARKLE.baseUrl;
|
|
return cfg;
|
|
}
|
|
} catch (e) {}
|
|
return { ...DEFAULT_SPARKLE };
|
|
}
|
|
|
|
export function saveSparkleConfig(cfg) {
|
|
localStorage.setItem(CONFIG.SPARKLE_CFG_KEY, JSON.stringify(cfg));
|
|
}
|
|
|
|
export function loadHistory() {
|
|
try {
|
|
const s = localStorage.getItem(CONFIG.HISTORY_STORAGE_KEY);
|
|
return s ? JSON.parse(s) : [];
|
|
} catch (e) { return []; }
|
|
}
|
|
|
|
export function saveHistory(history) {
|
|
try {
|
|
|
|
const slim = history
|
|
.filter(m => m.role === 'user' || m.role === 'assistant')
|
|
.map(m => ({
|
|
role: m.role,
|
|
content: typeof m.content === 'string' ? m.content.slice(0, 4000) : '',
|
|
thinking: typeof m.thinking === 'string' && m.thinking
|
|
? m.thinking.slice(0, 8000)
|
|
: undefined,
|
|
}));
|
|
localStorage.setItem(CONFIG.HISTORY_STORAGE_KEY, JSON.stringify(slim));
|
|
} catch (e) {}
|
|
}
|
|
|
|
export function clearHistoryStorage() {
|
|
localStorage.removeItem(CONFIG.HISTORY_STORAGE_KEY);
|
|
}
|
|
|
|
export function loadThinkingEnabled() {
|
|
return localStorage.getItem(CONFIG.THINKING_ENABLED_KEY) === '1';
|
|
}
|
|
|
|
export function saveThinkingEnabled(on) {
|
|
localStorage.setItem(CONFIG.THINKING_ENABLED_KEY, on ? '1' : '0');
|
|
}
|
|
|
|
export function loadCrossModuleEnabled() {
|
|
return localStorage.getItem(CONFIG.CROSS_MODULE_ENABLED_KEY) === '1';
|
|
}
|
|
|
|
export function saveCrossModuleEnabled(on) {
|
|
localStorage.setItem(CONFIG.CROSS_MODULE_ENABLED_KEY, on ? '1' : '0');
|
|
}
|
|
|
|
export function modelThinkingCapability(modelName) {
|
|
const m = String(modelName || '');
|
|
if (!m) return 'unknown';
|
|
const lower = m.toLowerCase();
|
|
if (/instruct-2507/i.test(m) && !/thinking/i.test(m)) return 'no';
|
|
if (/thinking-2507/i.test(m) || /[/_\-]thinking([/_\-]|$)/i.test(lower)) return 'yes';
|
|
|
|
if (/qwen3(\.|\d|$)/i.test(m) && !/instruct-2507/i.test(m)) return 'yes';
|
|
|
|
if (/glm-?(4\.5|4\.6|4\.7|5)/i.test(m) || /deepseek.*reasoner|deepseek-r1|deepseek-v[34]/i.test(lower)) return 'yes';
|
|
if (/claude.*(opus|sonnet|4)/i.test(lower)) return 'yes';
|
|
|
|
if (/qwen2\.5/i.test(m)) return 'no';
|
|
return 'unknown';
|
|
}
|
|
|
|
export const THRESHOLD_OPTIONS = [
|
|
{ value: 8000, label: '紧凑(8k)— 小 context 模型' },
|
|
{ value: 24000, label: '标准(24k)— 32k 模型' },
|
|
{ value: 48000, label: '宽松(48k)— 64k+ 模型' },
|
|
{ value: 96000, label: '超大(96k)— 128k 模型' },
|
|
{ value: 500000, label: '极限(500k)— 长上下文模型' },
|
|
{ value: 9999999, label: '关闭(不压缩)' },
|
|
];
|
|
|
|
const DEFAULT_THRESHOLD_BY_SOURCE = {
|
|
ollama: 24000,
|
|
omlx: 24000,
|
|
sparkle: 24000,
|
|
anthropic: 96000,
|
|
};
|
|
|
|
export function loadContextThreshold(source) {
|
|
const key = `${CONFIG.CONTEXT_THRESHOLD_KEY}_${source || 'ollama'}`;
|
|
const v = parseInt(localStorage.getItem(key), 10);
|
|
if (v > 0) return v;
|
|
return DEFAULT_THRESHOLD_BY_SOURCE[source] || 24000;
|
|
}
|
|
|
|
export function saveContextThreshold(source, v) {
|
|
const key = `${CONFIG.CONTEXT_THRESHOLD_KEY}_${source}`;
|
|
localStorage.setItem(key, String(v));
|
|
}
|
|
|
|
export function getCurrentModelInfo() {
|
|
const source = loadModelSource();
|
|
if (source === 'anthropic') {
|
|
const cfg = loadAnthropicConfig();
|
|
const tierModel = ({
|
|
fast: cfg.fastModel,
|
|
standard: cfg.standardModel,
|
|
pro: cfg.proModel,
|
|
})[cfg.currentTier] || cfg.fastModel;
|
|
const tierLabel = ({
|
|
fast: '快速档',
|
|
standard: '平衡档',
|
|
pro: '增强档',
|
|
})[cfg.currentTier] || '';
|
|
return {
|
|
source,
|
|
sourceLabel: '云端',
|
|
model: tierModel,
|
|
tier: cfg.currentTier,
|
|
tierLabel,
|
|
protocol: cfg.protocol === 'openai' ? 'openai' : 'anthropic',
|
|
isLocal: false,
|
|
};
|
|
}
|
|
if (source === 'omlx') {
|
|
const cfg = loadOmlxConfig();
|
|
return {
|
|
source,
|
|
sourceLabel: '本地',
|
|
model: cfg.model,
|
|
isLocal: true,
|
|
};
|
|
}
|
|
if (source === 'sparkle') {
|
|
const cfg = loadSparkleConfig();
|
|
return {
|
|
source,
|
|
sourceLabel: '本地',
|
|
model: cfg.model,
|
|
isLocal: true,
|
|
};
|
|
}
|
|
const cfg = loadOllamaConfig();
|
|
return {
|
|
source,
|
|
sourceLabel: '本地',
|
|
model: cfg.model,
|
|
isLocal: true,
|
|
};
|
|
}
|
|
|
|
export function ollamaUrl() {
|
|
const cfg = loadOllamaConfig();
|
|
const base = String(cfg.baseUrl || DEFAULT_OLLAMA.baseUrl).replace(/\/+$/, '');
|
|
return `${base}/api/chat`;
|
|
}
|
|
|
|
export function anthropicUrl() {
|
|
const cfg = loadAnthropicConfig();
|
|
const base = (cfg.baseUrl || '').replace(/\/+$/, '');
|
|
if ((cfg.protocol || 'anthropic') === 'openai') {
|
|
return `${base}/chat/completions`;
|
|
}
|
|
return `${base}/v1/messages`;
|
|
}
|
|
|
|
export function cloudProtocol() {
|
|
const cfg = loadAnthropicConfig();
|
|
return cfg.protocol === 'openai' ? 'openai' : 'anthropic';
|
|
}
|
|
|
|
export function omlxUrl() {
|
|
const cfg = loadOmlxConfig();
|
|
const base = String(cfg.baseUrl || DEFAULT_OMLX.baseUrl).replace(/\/+$/, '');
|
|
return `${base}${CONFIG.OMLX_CHAT_PATH}`;
|
|
}
|
|
|
|
export function sparkleUrl() {
|
|
const cfg = loadSparkleConfig();
|
|
const base = String(cfg.baseUrl || DEFAULT_SPARKLE.baseUrl).replace(/\/+$/, '');
|
|
return `${base}${CONFIG.SPARKLE_CHAT_PATH}`;
|
|
}
|