dskcode 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
7
7
  [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
8
8
 
9
+ ![dskcode](https://raw.githubusercontent.com/Awu12277/deepseek-agent-cli/refs/heads/main/public/dscoderun.gif)
9
10
  ---
10
11
 
11
12
  ## 特性
@@ -68,9 +68,7 @@ function calculateCost(usage, model) {
68
68
  };
69
69
  }
70
70
  function formatCost(cost) {
71
- const total = cost.totalCost;
72
- const formatted = total < 0.01 ? total.toFixed(6) : total.toFixed(4);
73
- return `\u2248\xA5${formatted}`;
71
+ return `\u2248\xA5${cost.totalCost.toFixed(4)}`;
74
72
  }
75
73
 
76
74
  export {
@@ -82,4 +80,4 @@ export {
82
80
  calculateCost,
83
81
  formatCost
84
82
  };
85
- //# sourceMappingURL=chunk-DIZMSV5H.js.map
83
+ //# sourceMappingURL=chunk-7QS2XBBX.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider/models.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\r\n// 模型定义与校验\r\n// ---------------------------------------------------------------------------\r\n\r\nimport type { ModelId, ModelMeta } from \"./types.js\";\r\n\r\n/** 支持的模型及其元数据 */\r\nexport const SUPPORTED_MODELS: Record<ModelId, ModelMeta> = {\r\n \"deepseek-v4-flash\": {\r\n id: \"deepseek-v4-flash\",\r\n displayName: \"DeepSeek V4 Flash\",\r\n /** 上下文窗口大小(token) */\r\n contextWindow: 1_000_000,\r\n /** 输入价格(元 / 百万 token,缓存未命中) */\r\n inputPricePerMillion: 1,\r\n /** 输出价格(元 / 百万 token) */\r\n outputPricePerMillion: 2,\r\n /** 输入价格(元 / 百万 token,缓存命中) */\r\n cacheHitPricePerMillion: 0.02,\r\n },\r\n \"deepseek-v4-pro\": {\r\n id: \"deepseek-v4-pro\",\r\n displayName: \"DeepSeek V4 Pro\",\r\n /** 上下文窗口大小(token) */\r\n contextWindow: 1_000_000,\r\n /** 输入价格(元 / 百万 token,缓存未命中) */\r\n inputPricePerMillion: 3,\r\n /** 输出价格(元 / 百万 token) */\r\n outputPricePerMillion: 6,\r\n /** 输入价格(元 / 百万 token,缓存命中) */\r\n cacheHitPricePerMillion: 0.025,\r\n },\r\n};\r\n\r\n/** 支持的模型 ID 列表 */\r\nexport const SUPPORTED_MODEL_IDS: string[] = Object.keys(SUPPORTED_MODELS);\r\n\r\n/**\r\n * 判断模型标识是否受支持。\r\n * dskcode 仅支持 deepseek-v4-flash 和 deepseek-v4-pro 两个模型。\r\n */\r\nexport function isSupportedModel(model: string): model is ModelId {\r\n return model in SUPPORTED_MODELS;\r\n}\r\n\r\n/** 获取模型元数据(已校验) */\r\nexport function getModelMeta(model: ModelId): ModelMeta {\r\n return SUPPORTED_MODELS[model];\r\n}\r\n\r\n/**\r\n * CJK 字符 Unicode 范围检测。\r\n * 覆盖 CJK 统一汉字、扩展 A/B、兼容汉字等。\r\n */\r\nfunction isCJK(char: string): boolean {\r\n const code = char.codePointAt(0)!;\r\n // CJK 统一汉字\r\n return (\r\n (code >= 0x4e00 && code <= 0x9fff) ||\r\n // CJK 扩展 A\r\n (code >= 0x3400 && code <= 0x4dbf) ||\r\n // CJK 扩展 B 及其他\r\n (code >= 0x20000 && code <= 0x2a6df) ||\r\n // CJK 兼容汉字\r\n (code >= 0xf900 && code <= 0xfaff) ||\r\n // 全角字符\r\n (code >= 0xff01 && code <= 0xff60)\r\n );\r\n}\r\n\r\n/**\r\n * 估算文本的 token 数量。\r\n *\r\n * 根据 DeepSeek 官方文档的换算比例:\r\n * - 1 个英文字符 ≈ 0.3 个 token(即约 3.3 个字符 ≈ 1 token)\r\n * - 1 个中文字符 ≈ 0.6 个 token(即约 1.7 个字符 ≈ 1 token)\r\n *\r\n * 对文本中的 CJK 和非 CJK 字符分别估算后求和。\r\n */\r\nexport function estimateTokens(text: string): number {\r\n let cjkCount = 0;\r\n let otherCount = 0;\r\n for (const char of text) {\r\n if (isCJK(char)) {\r\n cjkCount++;\r\n } else {\r\n otherCount++;\r\n }\r\n }\r\n // CJK: 1 字符 ≈ 0.6 token → token 数 ≈ cjkCount × 0.6\r\n // 其他: 1 字符 ≈ 0.3 token → token 数 ≈ otherCount × 0.3\r\n const tokens = cjkCount * 0.6 + otherCount * 0.3;\r\n return Math.max(1, Math.ceil(tokens));\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// 费用计算\r\n// ---------------------------------------------------------------------------\r\n\r\nimport type { UsageInfo, CostInfo } from \"./types.js\";\r\n\r\n/**\r\n * 根据模型和 Token 使用量计算费用。\r\n *\r\n * 计费公式:\r\n * 输入费用(缓存未命中) = (promptTokens - cachedPromptTokens) × inputPrice / 1,000,000\r\n * 缓存命中费用 = cachedPromptTokens × cacheHitPrice / 1,000,000\r\n * 输出费用 = completionTokens × outputPrice / 1,000,000\r\n * 总费用 = 输入费用 + 缓存命中费用 + 输出费用\r\n *\r\n * @param usage Token 使用统计\r\n * @param model 模型标识\r\n * @returns 费用明细(单位:元)\r\n */\r\nexport function calculateCost(usage: UsageInfo, model: ModelId): CostInfo {\r\n const meta = SUPPORTED_MODELS[model];\r\n const cached = usage.cachedPromptTokens ?? 0;\r\n const nonCached = usage.promptTokens - cached;\r\n\r\n const inputCost = (nonCached * meta.inputPricePerMillion) / 1_000_000;\r\n const cacheHitCost = (cached * meta.cacheHitPricePerMillion) / 1_000_000;\r\n const outputCost = (usage.completionTokens * meta.outputPricePerMillion) / 1_000_000;\r\n\r\n return {\r\n inputCost,\r\n cacheHitCost,\r\n outputCost,\r\n totalCost: inputCost + cacheHitCost + outputCost,\r\n };\r\n}\r\n\r\n/**\r\n * 将费用格式化为易读的字符串,只展示合计费用。\r\n *\r\n * - 小于 0.01 元时显示到小数点后 6 位(如 ¥0.000032)\r\n * - 大于等于 0.01 元时显示到小数点后 4 位(如 ¥0.1234)\r\n */\r\nexport function formatCost(cost: CostInfo): string {\r\n const total = cost.totalCost;\r\n const formatted = total < 0.01 ? total.toFixed(6) : total.toFixed(4);\r\n return `≈¥${formatted}`;\r\n}"],"mappings":";AAOO,IAAM,mBAA+C;AAAA,EAC1D,qBAAqB;AAAA,IACnB,IAAI;AAAA,IACJ,aAAa;AAAA;AAAA,IAEb,eAAe;AAAA;AAAA,IAEf,sBAAsB;AAAA;AAAA,IAEtB,uBAAuB;AAAA;AAAA,IAEvB,yBAAyB;AAAA,EAC3B;AAAA,EACA,mBAAmB;AAAA,IACjB,IAAI;AAAA,IACJ,aAAa;AAAA;AAAA,IAEb,eAAe;AAAA;AAAA,IAEf,sBAAsB;AAAA;AAAA,IAEtB,uBAAuB;AAAA;AAAA,IAEvB,yBAAyB;AAAA,EAC3B;AACF;AAGO,IAAM,sBAAgC,OAAO,KAAK,gBAAgB;AAMlE,SAAS,iBAAiB,OAAiC;AAChE,SAAO,SAAS;AAClB;AAGO,SAAS,aAAa,OAA2B;AACtD,SAAO,iBAAiB,KAAK;AAC/B;AAMA,SAAS,MAAM,MAAuB;AACpC,QAAM,OAAO,KAAK,YAAY,CAAC;AAE/B,SACG,QAAQ,SAAU,QAAQ;AAAA,EAE1B,QAAQ,SAAU,QAAQ;AAAA,EAE1B,QAAQ,UAAW,QAAQ;AAAA,EAE3B,QAAQ,SAAU,QAAQ;AAAA,EAE1B,QAAQ,SAAU,QAAQ;AAE/B;AAWO,SAAS,eAAe,MAAsB;AACnD,MAAI,WAAW;AACf,MAAI,aAAa;AACjB,aAAW,QAAQ,MAAM;AACvB,QAAI,MAAM,IAAI,GAAG;AACf;AAAA,IACF,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAS,WAAW,MAAM,aAAa;AAC7C,SAAO,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,CAAC;AACtC;AAqBO,SAAS,cAAc,OAAkB,OAA0B;AACxE,QAAM,OAAO,iBAAiB,KAAK;AACnC,QAAM,SAAS,MAAM,sBAAsB;AAC3C,QAAM,YAAY,MAAM,eAAe;AAEvC,QAAM,YAAa,YAAY,KAAK,uBAAwB;AAC5D,QAAM,eAAgB,SAAS,KAAK,0BAA2B;AAC/D,QAAM,aAAc,MAAM,mBAAmB,KAAK,wBAAyB;AAE3E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,YAAY,eAAe;AAAA,EACxC;AACF;AAQO,SAAS,WAAW,MAAwB;AACjD,QAAM,QAAQ,KAAK;AACnB,QAAM,YAAY,QAAQ,OAAO,MAAM,QAAQ,CAAC,IAAI,MAAM,QAAQ,CAAC;AACnE,SAAO,aAAK,SAAS;AACvB;","names":[]}
1
+ {"version":3,"sources":["../src/provider/models.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\r\n// 模型定义与校验\r\n// ---------------------------------------------------------------------------\r\n\r\nimport type { ModelId, ModelMeta } from \"./types.js\";\r\n\r\n/** 支持的模型及其元数据 */\r\nexport const SUPPORTED_MODELS: Record<ModelId, ModelMeta> = {\r\n \"deepseek-v4-flash\": {\r\n id: \"deepseek-v4-flash\",\r\n displayName: \"DeepSeek V4 Flash\",\r\n /** 上下文窗口大小(token) */\r\n contextWindow: 1_000_000,\r\n /** 输入价格(元 / 百万 token,缓存未命中) */\r\n inputPricePerMillion: 1,\r\n /** 输出价格(元 / 百万 token) */\r\n outputPricePerMillion: 2,\r\n /** 输入价格(元 / 百万 token,缓存命中) */\r\n cacheHitPricePerMillion: 0.02,\r\n },\r\n \"deepseek-v4-pro\": {\r\n id: \"deepseek-v4-pro\",\r\n displayName: \"DeepSeek V4 Pro\",\r\n /** 上下文窗口大小(token) */\r\n contextWindow: 1_000_000,\r\n /** 输入价格(元 / 百万 token,缓存未命中) */\r\n inputPricePerMillion: 3,\r\n /** 输出价格(元 / 百万 token) */\r\n outputPricePerMillion: 6,\r\n /** 输入价格(元 / 百万 token,缓存命中) */\r\n cacheHitPricePerMillion: 0.025,\r\n },\r\n};\r\n\r\n/** 支持的模型 ID 列表 */\r\nexport const SUPPORTED_MODEL_IDS: string[] = Object.keys(SUPPORTED_MODELS);\r\n\r\n/**\r\n * 判断模型标识是否受支持。\r\n * dskcode 仅支持 deepseek-v4-flash 和 deepseek-v4-pro 两个模型。\r\n */\r\nexport function isSupportedModel(model: string): model is ModelId {\r\n return model in SUPPORTED_MODELS;\r\n}\r\n\r\n/** 获取模型元数据(已校验) */\r\nexport function getModelMeta(model: ModelId): ModelMeta {\r\n return SUPPORTED_MODELS[model];\r\n}\r\n\r\n/**\r\n * CJK 字符 Unicode 范围检测。\r\n * 覆盖 CJK 统一汉字、扩展 A/B、兼容汉字等。\r\n */\r\nfunction isCJK(char: string): boolean {\r\n const code = char.codePointAt(0)!;\r\n // CJK 统一汉字\r\n return (\r\n (code >= 0x4e00 && code <= 0x9fff) ||\r\n // CJK 扩展 A\r\n (code >= 0x3400 && code <= 0x4dbf) ||\r\n // CJK 扩展 B 及其他\r\n (code >= 0x20000 && code <= 0x2a6df) ||\r\n // CJK 兼容汉字\r\n (code >= 0xf900 && code <= 0xfaff) ||\r\n // 全角字符\r\n (code >= 0xff01 && code <= 0xff60)\r\n );\r\n}\r\n\r\n/**\r\n * 估算文本的 token 数量。\r\n *\r\n * 根据 DeepSeek 官方文档的换算比例:\r\n * - 1 个英文字符 ≈ 0.3 个 token(即约 3.3 个字符 ≈ 1 token)\r\n * - 1 个中文字符 ≈ 0.6 个 token(即约 1.7 个字符 ≈ 1 token)\r\n *\r\n * 对文本中的 CJK 和非 CJK 字符分别估算后求和。\r\n */\r\nexport function estimateTokens(text: string): number {\r\n let cjkCount = 0;\r\n let otherCount = 0;\r\n for (const char of text) {\r\n if (isCJK(char)) {\r\n cjkCount++;\r\n } else {\r\n otherCount++;\r\n }\r\n }\r\n // CJK: 1 字符 ≈ 0.6 token → token 数 ≈ cjkCount × 0.6\r\n // 其他: 1 字符 ≈ 0.3 token → token 数 ≈ otherCount × 0.3\r\n const tokens = cjkCount * 0.6 + otherCount * 0.3;\r\n return Math.max(1, Math.ceil(tokens));\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// 费用计算\r\n// ---------------------------------------------------------------------------\r\n\r\nimport type { UsageInfo, CostInfo } from \"./types.js\";\r\n\r\n/**\r\n * 根据模型和 Token 使用量计算费用。\r\n *\r\n * 计费公式:\r\n * 输入费用(缓存未命中) = (promptTokens - cachedPromptTokens) × inputPrice / 1,000,000\r\n * 缓存命中费用 = cachedPromptTokens × cacheHitPrice / 1,000,000\r\n * 输出费用 = completionTokens × outputPrice / 1,000,000\r\n * 总费用 = 输入费用 + 缓存命中费用 + 输出费用\r\n *\r\n * @param usage Token 使用统计\r\n * @param model 模型标识\r\n * @returns 费用明细(单位:元)\r\n */\r\nexport function calculateCost(usage: UsageInfo, model: ModelId): CostInfo {\r\n const meta = SUPPORTED_MODELS[model];\r\n const cached = usage.cachedPromptTokens ?? 0;\r\n const nonCached = usage.promptTokens - cached;\r\n\r\n const inputCost = (nonCached * meta.inputPricePerMillion) / 1_000_000;\r\n const cacheHitCost = (cached * meta.cacheHitPricePerMillion) / 1_000_000;\r\n const outputCost = (usage.completionTokens * meta.outputPricePerMillion) / 1_000_000;\r\n\r\n return {\r\n inputCost,\r\n cacheHitCost,\r\n outputCost,\r\n totalCost: inputCost + cacheHitCost + outputCost,\r\n };\r\n}\r\n\r\n/**\r\n * 将费用格式化为易读的字符串,保留4位小数。\r\n */\r\nexport function formatCost(cost: CostInfo): string {\r\n return `≈¥${cost.totalCost.toFixed(4)}`;\r\n}"],"mappings":";AAOO,IAAM,mBAA+C;AAAA,EAC1D,qBAAqB;AAAA,IACnB,IAAI;AAAA,IACJ,aAAa;AAAA;AAAA,IAEb,eAAe;AAAA;AAAA,IAEf,sBAAsB;AAAA;AAAA,IAEtB,uBAAuB;AAAA;AAAA,IAEvB,yBAAyB;AAAA,EAC3B;AAAA,EACA,mBAAmB;AAAA,IACjB,IAAI;AAAA,IACJ,aAAa;AAAA;AAAA,IAEb,eAAe;AAAA;AAAA,IAEf,sBAAsB;AAAA;AAAA,IAEtB,uBAAuB;AAAA;AAAA,IAEvB,yBAAyB;AAAA,EAC3B;AACF;AAGO,IAAM,sBAAgC,OAAO,KAAK,gBAAgB;AAMlE,SAAS,iBAAiB,OAAiC;AAChE,SAAO,SAAS;AAClB;AAGO,SAAS,aAAa,OAA2B;AACtD,SAAO,iBAAiB,KAAK;AAC/B;AAMA,SAAS,MAAM,MAAuB;AACpC,QAAM,OAAO,KAAK,YAAY,CAAC;AAE/B,SACG,QAAQ,SAAU,QAAQ;AAAA,EAE1B,QAAQ,SAAU,QAAQ;AAAA,EAE1B,QAAQ,UAAW,QAAQ;AAAA,EAE3B,QAAQ,SAAU,QAAQ;AAAA,EAE1B,QAAQ,SAAU,QAAQ;AAE/B;AAWO,SAAS,eAAe,MAAsB;AACnD,MAAI,WAAW;AACf,MAAI,aAAa;AACjB,aAAW,QAAQ,MAAM;AACvB,QAAI,MAAM,IAAI,GAAG;AACf;AAAA,IACF,OAAO;AACL;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAS,WAAW,MAAM,aAAa;AAC7C,SAAO,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,CAAC;AACtC;AAqBO,SAAS,cAAc,OAAkB,OAA0B;AACxE,QAAM,OAAO,iBAAiB,KAAK;AACnC,QAAM,SAAS,MAAM,sBAAsB;AAC3C,QAAM,YAAY,MAAM,eAAe;AAEvC,QAAM,YAAa,YAAY,KAAK,uBAAwB;AAC5D,QAAM,eAAgB,SAAS,KAAK,0BAA2B;AAC/D,QAAM,aAAc,MAAM,mBAAmB,KAAK,wBAAyB;AAE3E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,YAAY,eAAe;AAAA,EACxC;AACF;AAKO,SAAS,WAAW,MAAwB;AACjD,SAAO,aAAK,KAAK,UAAU,QAAQ,CAAC,CAAC;AACvC;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  estimateTokens,
3
3
  getModelMeta
4
- } from "./chunk-DIZMSV5H.js";
4
+ } from "./chunk-7QS2XBBX.js";
5
5
 
6
6
  // src/provider/errors.ts
7
7
  var ProviderError = class extends Error {
@@ -623,4 +623,4 @@ export {
623
623
  ModelNotSupportedError,
624
624
  DeepSeekProvider
625
625
  };
626
- //# sourceMappingURL=chunk-FODNQIT4.js.map
626
+ //# sourceMappingURL=chunk-HWZZJB6I.js.map
@@ -0,0 +1,8 @@
1
+ import {
2
+ DeepSeekProvider
3
+ } from "./chunk-HWZZJB6I.js";
4
+ import "./chunk-7QS2XBBX.js";
5
+ export {
6
+ DeepSeekProvider
7
+ };
8
+ //# sourceMappingURL=deepseek-OUOEENTK.js.map
package/dist/index.js CHANGED
@@ -2,13 +2,14 @@
2
2
  import {
3
3
  DeepSeekProvider,
4
4
  ModelNotSupportedError
5
- } from "./chunk-FODNQIT4.js";
5
+ } from "./chunk-HWZZJB6I.js";
6
6
  import {
7
+ SUPPORTED_MODELS,
7
8
  calculateCost,
8
9
  estimateTokens,
9
10
  getModelMeta,
10
11
  isSupportedModel
11
- } from "./chunk-DIZMSV5H.js";
12
+ } from "./chunk-7QS2XBBX.js";
12
13
 
13
14
  // src/cli/index.tsx
14
15
  import { Command } from "commander";
@@ -303,6 +304,34 @@ async function saveApiKey(apiKey) {
303
304
  await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
304
305
  return configFile;
305
306
  }
307
+ async function saveModelConfig(model) {
308
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
309
+ const configDir = join(home, ".dskcode");
310
+ const configFile = join(configDir, "settings.json");
311
+ await mkdir(configDir, { recursive: true });
312
+ let configData;
313
+ try {
314
+ const raw = await readFile(configFile, "utf-8");
315
+ configData = JSON.parse(raw);
316
+ } catch {
317
+ configData = structuredClone(defaultConfig);
318
+ }
319
+ const providers = configData.providers ?? [];
320
+ const defaultProviderName = configData.defaultProvider ?? "deepseek";
321
+ const existing = providers.find((p) => p.name === defaultProviderName);
322
+ if (existing) {
323
+ existing.model = model;
324
+ } else {
325
+ providers.push({
326
+ name: defaultProviderName,
327
+ baseUrl: "https://api.deepseek.com",
328
+ model
329
+ });
330
+ }
331
+ configData.providers = providers;
332
+ await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
333
+ return configFile;
334
+ }
306
335
  async function saveStockConfig(symbols) {
307
336
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
308
337
  const configDir = join(home, ".dskcode");
@@ -820,10 +849,7 @@ function createEmptyDailySummary(date) {
820
849
  };
821
850
  }
822
851
  function formatMoney(yuan) {
823
- if (yuan === 0) return "\xA50.00";
824
- if (yuan < 0.01) return `\xA5${yuan.toFixed(6)}`;
825
- if (yuan < 1) return `\xA5${yuan.toFixed(4)}`;
826
- return `\xA5${yuan.toFixed(2)}`;
852
+ return `\xA5${yuan.toFixed(4)}`;
827
853
  }
828
854
 
829
855
  // src/ui/ToolCallBlock.tsx
@@ -1308,6 +1334,14 @@ var IDLE_GRADIENT_STOPS = [
1308
1334
  [189, 147, 249]
1309
1335
  // #bd93f9
1310
1336
  ];
1337
+ var CMD_TIP_GRADIENT_STOPS = [
1338
+ [255, 245, 180],
1339
+ // #fff5b4 浅柠黄
1340
+ [255, 210, 80],
1341
+ // #ffd250 暖黄
1342
+ [255, 150, 50]
1343
+ // #ff9632 橙色
1344
+ ];
1311
1345
  var STREAMING_GRADIENT_STOPS = [
1312
1346
  [255, 191, 0],
1313
1347
  // #ffbf00 琥珀金
@@ -1324,7 +1358,11 @@ var GRADIENT_ANIMATION = {
1324
1358
  /** 流式占位符动画:每帧相位步进 */
1325
1359
  streamingPhaseStep: 0.05,
1326
1360
  /** 流式占位符动画:帧间隔(ms) */
1327
- streamingInterval: 40
1361
+ streamingInterval: 40,
1362
+ /** 命令提示条动画:每帧相位步进 */
1363
+ cmdTipPhaseStep: 0.05,
1364
+ /** 命令提示条动画:帧间隔(ms) */
1365
+ cmdTipInterval: 40
1328
1366
  };
1329
1367
  var SKIP_CHARS = /* @__PURE__ */ new Set([" ", "~", ".", "\u3002", "\uFF01", "\u{1F447}"]);
1330
1368
  function lerpStopsToHex(t, stops) {
@@ -1351,7 +1389,7 @@ function getGradientColors(text, phase, stops) {
1351
1389
  }
1352
1390
 
1353
1391
  // src/ui/ChatSession.tsx
1354
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1392
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1355
1393
  var commandRegistry = /* @__PURE__ */ new Map();
1356
1394
  function registerCommand(name, cmd) {
1357
1395
  commandRegistry.set(name, cmd);
@@ -1374,6 +1412,7 @@ registerCommand("/help", {
1374
1412
  });
1375
1413
  registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
1376
1414
  registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.1.10" }) });
1415
+ registerCommand("/model", { desc: "\u5207\u6362\u6A21\u578B", handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /model \u8FDB\u5165\u9009\u62E9\u754C\u9762" }) });
1377
1416
  registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
1378
1417
  registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
1379
1418
  var STREAMING_PLACEHOLDERS = [
@@ -1388,7 +1427,7 @@ var IDLE_PLACEHOLDERS = [
1388
1427
  "\u60F3\u5E72\u5565\uFF1F\u76F4\u63A5\u8BF4~",
1389
1428
  "\u6765\u5427\uFF0C\u5429\u5490\u70B9\u5565",
1390
1429
  "\u968F\u65F6\u5F85\u547D...",
1391
- "\u6233\u8FD9\u91CC\u5F00\u804A \u{1F447}",
1430
+ "\u6233\u8FD9\u91CC\u5F00\u804A...",
1392
1431
  "\u7B49\u4F60\u5F00\u53E3...",
1393
1432
  "\u5C3D\u7BA1\u4F7F\u5524~"
1394
1433
  ];
@@ -1410,6 +1449,7 @@ function ChatSession({
1410
1449
  const dividerWidth = Math.max(termWidth - 2, 1);
1411
1450
  const [offset, setOffset] = useState3(0);
1412
1451
  const [displayMessages, setDisplayMessages] = useState3([]);
1452
+ const [staticKey, setStaticKey] = useState3(0);
1413
1453
  const [input, setInput] = useState3("");
1414
1454
  const [balance, setBalance] = useState3(null);
1415
1455
  const [balanceLoading, setBalanceLoading] = useState3(false);
@@ -1426,8 +1466,16 @@ function ChatSession({
1426
1466
  const [currentUsage, setCurrentUsage] = useState3(void 0);
1427
1467
  const [currentElapsed, setCurrentElapsed] = useState3(void 0);
1428
1468
  const [currentCost, setCurrentCost] = useState3(void 0);
1429
- const [currentModel, setCurrentModel] = useState3(void 0);
1469
+ const [activeModel, setActiveModel] = useState3(model);
1470
+ const [streamingModel, setStreamingModel] = useState3(void 0);
1430
1471
  const [streamError, setStreamError] = useState3(void 0);
1472
+ const cmdTips = Array.from(getRegisteredCommands()).filter(([name]) => name !== "/exit" && name !== "/quit").map(([name, cmd]) => ({ name, desc: cmd.desc }));
1473
+ const [cmdTipIndex, setCmdTipIndex] = useState3(0);
1474
+ const [cmdTipGradientColors, setCmdTipGradientColors] = useState3([]);
1475
+ const cmdTipPhaseRef = useRef2(0);
1476
+ const [selectingModel, setSelectingModel] = useState3(false);
1477
+ const [modelSelectIndex, setModelSelectIndex] = useState3(0);
1478
+ const modelOptions = ["deepseek-v4-flash", "deepseek-v4-pro"];
1431
1479
  const sessionRef = useRef2(null);
1432
1480
  const abortRef = useRef2(null);
1433
1481
  const currentContentRef = useRef2("");
@@ -1443,6 +1491,34 @@ function ChatSession({
1443
1491
  useInput(
1444
1492
  useCallback2(
1445
1493
  (_input, key) => {
1494
+ if (selectingModel) {
1495
+ if (key.upArrow) {
1496
+ setModelSelectIndex((prev) => (prev - 1 + modelOptions.length) % modelOptions.length);
1497
+ } else if (key.downArrow) {
1498
+ setModelSelectIndex((prev) => (prev + 1) % modelOptions.length);
1499
+ } else if (key.return) {
1500
+ const selected = modelOptions[modelSelectIndex];
1501
+ if (selected === activeModel) {
1502
+ setDisplayMessages((prev) => [
1503
+ ...prev,
1504
+ { role: "assistant", content: `\u5DF2\u7ECF\u5728\u4F7F\u7528 ${SUPPORTED_MODELS[selected].displayName}` }
1505
+ ]);
1506
+ } else {
1507
+ setActiveModel(selected);
1508
+ sessionRef.current?.reset();
1509
+ saveModelConfig(selected).catch(() => {
1510
+ });
1511
+ setDisplayMessages((prev) => [
1512
+ ...prev,
1513
+ { role: "assistant", content: `\u6A21\u578B\u5DF2\u5207\u6362\u4E3A ${SUPPORTED_MODELS[selected].displayName}\uFF08${selected}\uFF09` }
1514
+ ]);
1515
+ }
1516
+ setSelectingModel(false);
1517
+ } else if (key.escape) {
1518
+ setSelectingModel(false);
1519
+ }
1520
+ return;
1521
+ }
1446
1522
  if (key.ctrl && _input === "c") {
1447
1523
  if (isStreaming) {
1448
1524
  abortRef.current?.abort();
@@ -1455,9 +1531,31 @@ function ChatSession({
1455
1531
  setInput(_input);
1456
1532
  }
1457
1533
  },
1458
- [isStreaming, handleCtrlC, input]
1534
+ [selectingModel, modelSelectIndex, modelOptions, activeModel, isStreaming, handleCtrlC, input]
1459
1535
  )
1460
1536
  );
1537
+ useEffect3(() => {
1538
+ if (cmdTips.length <= 1) return;
1539
+ const timer = setInterval(() => {
1540
+ setCmdTipIndex((prev) => (prev + 1) % cmdTips.length);
1541
+ }, 5e3);
1542
+ return () => clearInterval(timer);
1543
+ }, [cmdTips.length]);
1544
+ useEffect3(() => {
1545
+ const tip = cmdTips[cmdTipIndex % cmdTips.length];
1546
+ if (!tip) {
1547
+ setCmdTipGradientColors([]);
1548
+ return;
1549
+ }
1550
+ const text = `${tip.name} ${tip.desc}`;
1551
+ cmdTipPhaseRef.current = 0;
1552
+ setCmdTipGradientColors(getGradientColors(text, 1, CMD_TIP_GRADIENT_STOPS));
1553
+ const interval = setInterval(() => {
1554
+ cmdTipPhaseRef.current = (cmdTipPhaseRef.current + GRADIENT_ANIMATION.cmdTipPhaseStep) % 1;
1555
+ setCmdTipGradientColors(getGradientColors(text, 1 - cmdTipPhaseRef.current, CMD_TIP_GRADIENT_STOPS));
1556
+ }, GRADIENT_ANIMATION.cmdTipInterval);
1557
+ return () => clearInterval(interval);
1558
+ }, [cmdTipIndex, cmdTips.length]);
1461
1559
  useEffect3(() => {
1462
1560
  const timer = setInterval(() => {
1463
1561
  setOffset((prev) => (prev + 1) % CYBER_PALETTE.length);
@@ -1470,7 +1568,7 @@ function ChatSession({
1470
1568
  name: "deepseek",
1471
1569
  apiKey,
1472
1570
  baseUrl,
1473
- model
1571
+ model: activeModel
1474
1572
  });
1475
1573
  const tracker = externalCostTracker ?? new CostTracker();
1476
1574
  const session = new Session(provider, [], tracker, {
@@ -1480,12 +1578,12 @@ function ChatSession({
1480
1578
  return () => {
1481
1579
  sessionRef.current = null;
1482
1580
  };
1483
- }, [apiKey, baseUrl, model, externalCostTracker]);
1581
+ }, [apiKey, baseUrl, activeModel, externalCostTracker]);
1484
1582
  useEffect3(() => {
1485
1583
  if (!apiKey || !baseUrl) return;
1486
1584
  let cancelled = false;
1487
1585
  setBalanceLoading(true);
1488
- import("./deepseek-OIJOG3N5.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
1586
+ import("./deepseek-OUOEENTK.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
1489
1587
  const provider = new DeepSeekProvider2({
1490
1588
  apiKey,
1491
1589
  baseUrl,
@@ -1530,10 +1628,10 @@ function ChatSession({
1530
1628
  return;
1531
1629
  }
1532
1630
  gradientPhaseRef.current = 0;
1533
- setGradientColors(getGradientColors(idlePlaceholder, 0, IDLE_GRADIENT_STOPS));
1631
+ setGradientColors(getGradientColors(idlePlaceholder, 1, IDLE_GRADIENT_STOPS));
1534
1632
  const interval = setInterval(() => {
1535
1633
  gradientPhaseRef.current = (gradientPhaseRef.current + GRADIENT_ANIMATION.idlePhaseStep) % 1;
1536
- setGradientColors(getGradientColors(idlePlaceholder, gradientPhaseRef.current, IDLE_GRADIENT_STOPS));
1634
+ setGradientColors(getGradientColors(idlePlaceholder, 1 - gradientPhaseRef.current, IDLE_GRADIENT_STOPS));
1537
1635
  }, GRADIENT_ANIMATION.idleInterval);
1538
1636
  return () => clearInterval(interval);
1539
1637
  }, [isStreaming, idlePlaceholder]);
@@ -1543,10 +1641,10 @@ function ChatSession({
1543
1641
  return;
1544
1642
  }
1545
1643
  streamingPhaseRef.current = 0;
1546
- setStreamingGradientColors(getGradientColors(streamingPlaceholder, 0, STREAMING_GRADIENT_STOPS));
1644
+ setStreamingGradientColors(getGradientColors(streamingPlaceholder, 1, STREAMING_GRADIENT_STOPS));
1547
1645
  const interval = setInterval(() => {
1548
1646
  streamingPhaseRef.current = (streamingPhaseRef.current + GRADIENT_ANIMATION.streamingPhaseStep) % 1;
1549
- setStreamingGradientColors(getGradientColors(streamingPlaceholder, streamingPhaseRef.current, STREAMING_GRADIENT_STOPS));
1647
+ setStreamingGradientColors(getGradientColors(streamingPlaceholder, 1 - streamingPhaseRef.current, STREAMING_GRADIENT_STOPS));
1550
1648
  }, GRADIENT_ANIMATION.streamingInterval);
1551
1649
  return () => clearInterval(interval);
1552
1650
  }, [isStreaming, streamingPlaceholder]);
@@ -1554,6 +1652,13 @@ function ChatSession({
1554
1652
  const trimmed = value.trim();
1555
1653
  if (!trimmed) return;
1556
1654
  if (trimmed.startsWith("/")) {
1655
+ if (trimmed.toLowerCase() === "/model") {
1656
+ const curIdx = modelOptions.indexOf(activeModel);
1657
+ setModelSelectIndex(curIdx >= 0 ? curIdx : 0);
1658
+ setSelectingModel(true);
1659
+ setInput("");
1660
+ return;
1661
+ }
1557
1662
  const cmd = commandRegistry.get(trimmed.toLowerCase());
1558
1663
  if (cmd) {
1559
1664
  const result = cmd.handler();
@@ -1563,7 +1668,10 @@ function ChatSession({
1563
1668
  return;
1564
1669
  case "clear":
1565
1670
  setDisplayMessages([]);
1671
+ setStaticKey((prev) => prev + 1);
1566
1672
  setInput("");
1673
+ setStreamError(void 0);
1674
+ process.stdout.write("\x1B[2J\x1B[H\x1B[3J");
1567
1675
  sessionRef.current?.reset();
1568
1676
  return;
1569
1677
  case "navigate":
@@ -1613,7 +1721,7 @@ function ChatSession({
1613
1721
  setCurrentUsage(void 0);
1614
1722
  setCurrentElapsed(void 0);
1615
1723
  setCurrentCost(void 0);
1616
- setCurrentModel(void 0);
1724
+ setStreamingModel(void 0);
1617
1725
  setStreamError(void 0);
1618
1726
  currentContentRef.current = "";
1619
1727
  currentToolCallsRef.current = [];
@@ -1645,7 +1753,7 @@ function ChatSession({
1645
1753
  break;
1646
1754
  case "usage":
1647
1755
  setCurrentUsage(event.usage);
1648
- setCurrentModel(event.model);
1756
+ setStreamingModel(event.model);
1649
1757
  currentUsageRef.current = event.usage;
1650
1758
  currentModelRef.current = event.model;
1651
1759
  break;
@@ -1696,14 +1804,14 @@ function ChatSession({
1696
1804
  }
1697
1805
  }, [isStreaming, externalCostTracker]);
1698
1806
  useEffect3(() => {
1699
- if (currentUsage && currentModel && !currentCost) {
1700
- import("./models-NQGNKB4T.js").then(({ calculateCost: calculateCost2 }) => {
1701
- const cost = calculateCost2(currentUsage, currentModel);
1807
+ if (currentUsage && streamingModel && !currentCost) {
1808
+ import("./models-OL7DHYGK.js").then(({ calculateCost: calculateCost2 }) => {
1809
+ const cost = calculateCost2(currentUsage, streamingModel);
1702
1810
  setCurrentCost(cost.totalCost);
1703
1811
  currentCostRef.current = cost.totalCost;
1704
1812
  });
1705
1813
  }
1706
- }, [currentUsage, currentModel, currentCost]);
1814
+ }, [currentUsage, streamingModel, currentCost]);
1707
1815
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
1708
1816
  /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", marginBottom: 1, children: [
1709
1817
  /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
@@ -1725,8 +1833,17 @@ function ChatSession({
1725
1833
  ] }),
1726
1834
  /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
1727
1835
  " \u{1F527} \u6A21\u578B ",
1728
- model
1836
+ SUPPORTED_MODELS[activeModel]?.displayName ?? activeModel
1729
1837
  ] }),
1838
+ cmdTips.length > 0 && (() => {
1839
+ const tip = cmdTips[cmdTipIndex % cmdTips.length];
1840
+ if (!tip) return null;
1841
+ const text = `${tip.name} ${tip.desc}`;
1842
+ return /* @__PURE__ */ jsxs5(Text6, { children: [
1843
+ /* @__PURE__ */ jsx5(Text6, { color: "#808080", children: " \u{1F4A1} " }),
1844
+ cmdTipGradientColors.length > 0 ? text.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: cmdTipGradientColors[i] || void 0, children: ch }, i)) : /* @__PURE__ */ jsx5(Text6, { color: "#808080", children: text })
1845
+ ] });
1846
+ })(),
1730
1847
  verbose ? /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
1731
1848
  ] }),
1732
1849
  /* @__PURE__ */ jsxs5(Box5, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "flex-end", children: [
@@ -1741,7 +1858,7 @@ function ChatSession({
1741
1858
  /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: "\u{1F4CA} " }),
1742
1859
  /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
1743
1860
  "\u4ECA\u65E5 \xA5",
1744
- formatMoney(todayCost).replace("\xA5", "")
1861
+ todayCost.toFixed(2)
1745
1862
  ] })
1746
1863
  ] }) : null
1747
1864
  ] })
@@ -1768,7 +1885,7 @@ function ChatSession({
1768
1885
  },
1769
1886
  i
1770
1887
  );
1771
- } }),
1888
+ } }, staticKey),
1772
1889
  isStreaming && /* @__PURE__ */ jsx5(
1773
1890
  AssistantMessage,
1774
1891
  {
@@ -1783,20 +1900,54 @@ function ChatSession({
1783
1900
  streamError
1784
1901
  ] }) })
1785
1902
  ] }),
1786
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1787
- /* @__PURE__ */ jsxs5(Box5, { children: [
1788
- /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
1789
- /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: !input && !isStreaming && idlePlaceholder && gradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: idlePlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: gradientColors[i] ?? void 0, children: ch }, i)) }) : !input && isStreaming && streamingPlaceholder && streamingGradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: streamingPlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: streamingGradientColors[i] ?? void 0, children: ch }, i)) }) : /* @__PURE__ */ jsx5(
1790
- TextInput,
1791
- {
1792
- value: input,
1793
- onChange: setInput,
1794
- onSubmit: handleSubmit,
1795
- placeholder: ""
1796
- }
1797
- ) })
1903
+ selectingModel ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1904
+ /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
1905
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1906
+ /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ffff", children: "\u9009\u62E9\u6A21\u578B\uFF1A" }),
1907
+ modelOptions.map((id, i) => {
1908
+ const meta = SUPPORTED_MODELS[id];
1909
+ const isCurrent = id === activeModel;
1910
+ const isSelected = i === modelSelectIndex;
1911
+ const marker = isSelected ? " > " : " ";
1912
+ const suffix = isCurrent ? " (\u5F53\u524D)" : "";
1913
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
1914
+ /* @__PURE__ */ jsxs5(
1915
+ Text6,
1916
+ {
1917
+ color: isSelected ? "#00ff41" : void 0,
1918
+ bold: isSelected,
1919
+ children: [
1920
+ marker,
1921
+ meta.displayName,
1922
+ suffix
1923
+ ]
1924
+ }
1925
+ ),
1926
+ isSelected && /* @__PURE__ */ jsxs5(Text6, { color: "#808080", children: [
1927
+ " \u2014 ",
1928
+ id
1929
+ ] })
1930
+ ] }, id);
1931
+ }),
1932
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
1933
+ ] }),
1934
+ /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
1935
+ ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1936
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1937
+ /* @__PURE__ */ jsxs5(Box5, { children: [
1938
+ /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
1939
+ /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: !input && !isStreaming && idlePlaceholder && gradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: idlePlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: gradientColors[i] ?? void 0, children: ch }, i)) }) : !input && isStreaming && streamingPlaceholder && streamingGradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: streamingPlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: streamingGradientColors[i] ?? void 0, children: ch }, i)) }) : /* @__PURE__ */ jsx5(
1940
+ TextInput,
1941
+ {
1942
+ value: input,
1943
+ onChange: setInput,
1944
+ onSubmit: handleSubmit,
1945
+ placeholder: ""
1946
+ }
1947
+ ) })
1948
+ ] }),
1949
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) })
1798
1950
  ] }),
1799
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1800
1951
  doubleCtrlC && !isStreaming && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) }),
1801
1952
  isStreaming && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "yellow", dimColor: true, children: " \u63D0\u793A\uFF1A\u6309 Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42" }) })
1802
1953
  ] });