dskcode 0.1.10 → 0.1.11
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/dist/chunk-DIZMSV5H.js +85 -0
- package/dist/chunk-DIZMSV5H.js.map +1 -0
- package/dist/chunk-FODNQIT4.js +626 -0
- package/dist/chunk-FODNQIT4.js.map +1 -0
- package/dist/deepseek-OIJOG3N5.js +8 -0
- package/dist/deepseek-OIJOG3N5.js.map +1 -0
- package/dist/index.js +1329 -176
- package/dist/index.js.map +1 -1
- package/dist/models-NQGNKB4T.js +19 -0
- package/dist/models-NQGNKB4T.js.map +1 -0
- package/package.json +1 -1
- package/dist/deepseek-7Y2G6EKM.js +0 -385
- package/dist/deepseek-7Y2G6EKM.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
DeepSeekProvider,
|
|
4
|
+
ModelNotSupportedError
|
|
5
|
+
} from "./chunk-FODNQIT4.js";
|
|
6
|
+
import {
|
|
7
|
+
calculateCost,
|
|
8
|
+
estimateTokens,
|
|
9
|
+
getModelMeta,
|
|
10
|
+
isSupportedModel
|
|
11
|
+
} from "./chunk-DIZMSV5H.js";
|
|
2
12
|
|
|
3
13
|
// src/cli/index.tsx
|
|
4
14
|
import { Command } from "commander";
|
|
@@ -65,6 +75,12 @@ function mergeConfig(base, overlay) {
|
|
|
65
75
|
if (overlay.maxToolRounds !== void 0) {
|
|
66
76
|
result.maxToolRounds = overlay.maxToolRounds;
|
|
67
77
|
}
|
|
78
|
+
if (overlay.budgetLimit !== void 0) {
|
|
79
|
+
result.budgetLimit = overlay.budgetLimit;
|
|
80
|
+
}
|
|
81
|
+
if (overlay.tokenBudgetLimit !== void 0) {
|
|
82
|
+
result.tokenBudgetLimit = overlay.tokenBudgetLimit;
|
|
83
|
+
}
|
|
68
84
|
if (overlay.providers !== void 0) {
|
|
69
85
|
result.providers = overlay.providers;
|
|
70
86
|
}
|
|
@@ -85,7 +101,9 @@ var ENV_MAP = {
|
|
|
85
101
|
[`${ENV_PREFIX}VERBOSE`]: "verbose",
|
|
86
102
|
[`${ENV_PREFIX}MAX_TOKENS`]: "maxTokens",
|
|
87
103
|
[`${ENV_PREFIX}TEMPERATURE`]: "temperature",
|
|
88
|
-
[`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds"
|
|
104
|
+
[`${ENV_PREFIX}MAX_TOOL_ROUNDS`]: "maxToolRounds",
|
|
105
|
+
[`${ENV_PREFIX}BUDGET_LIMIT`]: "budgetLimit",
|
|
106
|
+
[`${ENV_PREFIX}TOKEN_BUDGET_LIMIT`]: "tokenBudgetLimit"
|
|
89
107
|
};
|
|
90
108
|
function applyEnvVars(config) {
|
|
91
109
|
const result = { ...config, providers: [...config.providers] };
|
|
@@ -99,9 +117,11 @@ function applyEnvVars(config) {
|
|
|
99
117
|
break;
|
|
100
118
|
}
|
|
101
119
|
case "maxTokens":
|
|
102
|
-
case "maxToolRounds":
|
|
120
|
+
case "maxToolRounds":
|
|
121
|
+
case "budgetLimit":
|
|
122
|
+
case "tokenBudgetLimit": {
|
|
103
123
|
const n = Number(raw);
|
|
104
|
-
if (Number.isFinite(n) && n
|
|
124
|
+
if (Number.isFinite(n) && n >= 0) {
|
|
105
125
|
result[configKey] = n;
|
|
106
126
|
}
|
|
107
127
|
break;
|
|
@@ -156,6 +176,12 @@ function applyCliOverrides(config, flags) {
|
|
|
156
176
|
if (flags.temperature !== void 0 && flags.temperature >= 0 && flags.temperature <= 2) {
|
|
157
177
|
result.temperature = flags.temperature;
|
|
158
178
|
}
|
|
179
|
+
if (flags.budgetLimit !== void 0 && flags.budgetLimit >= 0) {
|
|
180
|
+
result.budgetLimit = flags.budgetLimit;
|
|
181
|
+
}
|
|
182
|
+
if (flags.tokenBudgetLimit !== void 0 && flags.tokenBudgetLimit >= 0) {
|
|
183
|
+
result.tokenBudgetLimit = flags.tokenBudgetLimit;
|
|
184
|
+
}
|
|
159
185
|
return result;
|
|
160
186
|
}
|
|
161
187
|
function validateConfig(config) {
|
|
@@ -166,7 +192,7 @@ function validateConfig(config) {
|
|
|
166
192
|
message: "\u81F3\u5C11\u9700\u8981\u914D\u7F6E\u4E00\u4E2A Provider\u3002\u8BF7\u901A\u8FC7\u914D\u7F6E\u6587\u4EF6\u6216 DEEPSEEK_API_KEY \u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E\u3002"
|
|
167
193
|
});
|
|
168
194
|
}
|
|
169
|
-
const
|
|
195
|
+
const SUPPORTED_MODELS2 = ["deepseek-v4-flash", "deepseek-v4-pro"];
|
|
170
196
|
for (let i = 0; i < config.providers.length; i++) {
|
|
171
197
|
const p = config.providers[i];
|
|
172
198
|
if (!p.name) {
|
|
@@ -180,10 +206,10 @@ function validateConfig(config) {
|
|
|
180
206
|
field: `providers[${i}].model`,
|
|
181
207
|
message: `Provider "${p.name || i}" \u7F3A\u5C11 model \u5B57\u6BB5\u3002`
|
|
182
208
|
});
|
|
183
|
-
} else if (!
|
|
209
|
+
} else if (!SUPPORTED_MODELS2.includes(p.model)) {
|
|
184
210
|
errors.push({
|
|
185
211
|
field: `providers[${i}].model`,
|
|
186
|
-
message: `Provider "${p.name || i}" \u7684 model "${p.model}" \u4E0D\u53D7\u652F\u6301\u3002dskcode \u4EC5\u652F\u6301: ${
|
|
212
|
+
message: `Provider "${p.name || i}" \u7684 model "${p.model}" \u4E0D\u53D7\u652F\u6301\u3002dskcode \u4EC5\u652F\u6301: ${SUPPORTED_MODELS2.join(", ")}`
|
|
187
213
|
});
|
|
188
214
|
}
|
|
189
215
|
}
|
|
@@ -216,6 +242,18 @@ function validateConfig(config) {
|
|
|
216
242
|
message: "maxToolRounds \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 1\u3002"
|
|
217
243
|
});
|
|
218
244
|
}
|
|
245
|
+
if (config.budgetLimit !== void 0 && config.budgetLimit < 0) {
|
|
246
|
+
errors.push({
|
|
247
|
+
field: "budgetLimit",
|
|
248
|
+
message: "budgetLimit \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 0\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09\u3002"
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (config.tokenBudgetLimit !== void 0 && config.tokenBudgetLimit < 0) {
|
|
252
|
+
errors.push({
|
|
253
|
+
field: "tokenBudgetLimit",
|
|
254
|
+
message: "tokenBudgetLimit \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 0\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09\u3002"
|
|
255
|
+
});
|
|
256
|
+
}
|
|
219
257
|
return errors;
|
|
220
258
|
}
|
|
221
259
|
async function loadConfig(configPath) {
|
|
@@ -421,8 +459,8 @@ async function promptForApiKey() {
|
|
|
421
459
|
}
|
|
422
460
|
|
|
423
461
|
// src/cli/index.tsx
|
|
424
|
-
import { readFile as
|
|
425
|
-
import { join as
|
|
462
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
463
|
+
import { join as join3 } from "path";
|
|
426
464
|
|
|
427
465
|
// src/ui/RenderScope.tsx
|
|
428
466
|
import { render } from "ink";
|
|
@@ -435,6 +473,15 @@ function renderApp(node) {
|
|
|
435
473
|
import { Text } from "ink";
|
|
436
474
|
import InkSpinner from "ink-spinner";
|
|
437
475
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
476
|
+
function Spinner({ type = "dots", label }) {
|
|
477
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
478
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(InkSpinner, { type }) }),
|
|
479
|
+
label ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
480
|
+
" ",
|
|
481
|
+
label
|
|
482
|
+
] }) : null
|
|
483
|
+
] });
|
|
484
|
+
}
|
|
438
485
|
|
|
439
486
|
// src/ui/StatusMessage.tsx
|
|
440
487
|
import { Box, Text as Text2 } from "ink";
|
|
@@ -455,9 +502,9 @@ var LOGO_LINES = [
|
|
|
455
502
|
];
|
|
456
503
|
|
|
457
504
|
// src/ui/ChatSession.tsx
|
|
458
|
-
import { Box as
|
|
505
|
+
import { Box as Box5, Text as Text6, useInput, Static } from "ink";
|
|
459
506
|
import TextInput from "ink-text-input";
|
|
460
|
-
import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2 } from "react";
|
|
507
|
+
import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
461
508
|
|
|
462
509
|
// src/ui/useDoubleCtrlC.ts
|
|
463
510
|
import { useState as useState2, useCallback, useEffect as useEffect2, useRef } from "react";
|
|
@@ -487,8 +534,824 @@ function useDoubleCtrlC(onExit) {
|
|
|
487
534
|
return { doubleCtrlC, handleCtrlC };
|
|
488
535
|
}
|
|
489
536
|
|
|
490
|
-
// src/ui/
|
|
537
|
+
// src/ui/AssistantMessage.tsx
|
|
538
|
+
import { Box as Box4, Text as Text5 } from "ink";
|
|
539
|
+
|
|
540
|
+
// src/provider/cost-tracker.ts
|
|
541
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
542
|
+
import { join as join2 } from "path";
|
|
543
|
+
var CostTracker = class {
|
|
544
|
+
#costDir;
|
|
545
|
+
#budgetLimit;
|
|
546
|
+
#tokenBudgetLimit;
|
|
547
|
+
#onBudgetExceeded;
|
|
548
|
+
// 会话级统计(内存)
|
|
549
|
+
#sessionId;
|
|
550
|
+
#sessionStartedAt;
|
|
551
|
+
#sessionRecords = [];
|
|
552
|
+
// 日级统计(内存缓存,启动时从磁盘恢复)
|
|
553
|
+
#todayDate;
|
|
554
|
+
#todaySummary;
|
|
555
|
+
// 持久化标记
|
|
556
|
+
#dirty = false;
|
|
557
|
+
#flushInProgress = false;
|
|
558
|
+
constructor(options = {}) {
|
|
559
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
560
|
+
this.#costDir = options.costDir ?? join2(home, ".dskcode", "costs");
|
|
561
|
+
this.#budgetLimit = options.budgetLimit ?? 0;
|
|
562
|
+
this.#tokenBudgetLimit = options.tokenBudgetLimit ?? 0;
|
|
563
|
+
this.#onBudgetExceeded = options.onBudgetExceeded;
|
|
564
|
+
this.#sessionId = generateSessionId();
|
|
565
|
+
this.#sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
566
|
+
const today = getTodayStr();
|
|
567
|
+
this.#todayDate = today;
|
|
568
|
+
this.#todaySummary = createEmptyDailySummary(today);
|
|
569
|
+
}
|
|
570
|
+
// -----------------------------------------------------------------------
|
|
571
|
+
// 核心方法
|
|
572
|
+
// -----------------------------------------------------------------------
|
|
573
|
+
/**
|
|
574
|
+
* 记录一次 API 调用的 Token 使用量和成本。
|
|
575
|
+
* 自动计算费用,累加到会话级和日级统计。
|
|
576
|
+
*/
|
|
577
|
+
record(usage, model) {
|
|
578
|
+
const cost = calculateCost(usage, model);
|
|
579
|
+
const record = {
|
|
580
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
581
|
+
model,
|
|
582
|
+
usage: { ...usage },
|
|
583
|
+
cost: { ...cost }
|
|
584
|
+
};
|
|
585
|
+
this.#sessionRecords.push(record);
|
|
586
|
+
this.#ensureTodayBucket();
|
|
587
|
+
this.#addToDaily(record);
|
|
588
|
+
this.#dirty = true;
|
|
589
|
+
this.#checkBudget();
|
|
590
|
+
return cost;
|
|
591
|
+
}
|
|
592
|
+
// -----------------------------------------------------------------------
|
|
593
|
+
// 会话级查询
|
|
594
|
+
// -----------------------------------------------------------------------
|
|
595
|
+
/** 当前会话 ID */
|
|
596
|
+
get sessionId() {
|
|
597
|
+
return this.#sessionId;
|
|
598
|
+
}
|
|
599
|
+
/** 当前会话的所有成本记录 */
|
|
600
|
+
get records() {
|
|
601
|
+
return this.#sessionRecords;
|
|
602
|
+
}
|
|
603
|
+
/** 当前会话累计成本汇总 */
|
|
604
|
+
get sessionSummary() {
|
|
605
|
+
let totalPromptTokens = 0;
|
|
606
|
+
let totalCompletionTokens = 0;
|
|
607
|
+
let totalCachedTokens = 0;
|
|
608
|
+
let totalCost = 0;
|
|
609
|
+
for (const r of this.#sessionRecords) {
|
|
610
|
+
totalPromptTokens += r.usage.promptTokens;
|
|
611
|
+
totalCompletionTokens += r.usage.completionTokens;
|
|
612
|
+
totalCachedTokens += r.usage.cachedPromptTokens ?? 0;
|
|
613
|
+
totalCost += r.cost.totalCost;
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
sessionId: this.#sessionId,
|
|
617
|
+
startedAt: this.#sessionStartedAt,
|
|
618
|
+
totalPromptTokens,
|
|
619
|
+
totalCompletionTokens,
|
|
620
|
+
totalCachedTokens,
|
|
621
|
+
totalCost,
|
|
622
|
+
records: [...this.#sessionRecords]
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/** 当前会话总费用(元) */
|
|
626
|
+
get sessionTotalCost() {
|
|
627
|
+
return this.#sessionRecords.reduce((sum, r) => sum + r.cost.totalCost, 0);
|
|
628
|
+
}
|
|
629
|
+
/** 当前会话总 prompt token 数 */
|
|
630
|
+
get sessionPromptTokens() {
|
|
631
|
+
return this.#sessionRecords.reduce((sum, r) => sum + r.usage.promptTokens, 0);
|
|
632
|
+
}
|
|
633
|
+
/** 当前会话总 completion token 数 */
|
|
634
|
+
get sessionCompletionTokens() {
|
|
635
|
+
return this.#sessionRecords.reduce((sum, r) => sum + r.usage.completionTokens, 0);
|
|
636
|
+
}
|
|
637
|
+
/** 当前会话 API 调用次数 */
|
|
638
|
+
get sessionCallCount() {
|
|
639
|
+
return this.#sessionRecords.length;
|
|
640
|
+
}
|
|
641
|
+
// -----------------------------------------------------------------------
|
|
642
|
+
// 日级查询
|
|
643
|
+
// -----------------------------------------------------------------------
|
|
644
|
+
/** 今日成本汇总 */
|
|
645
|
+
get todaySummary() {
|
|
646
|
+
this.#ensureTodayBucket();
|
|
647
|
+
return { ...this.#todaySummary };
|
|
648
|
+
}
|
|
649
|
+
/** 今日总费用(元) */
|
|
650
|
+
get todayTotalCost() {
|
|
651
|
+
this.#ensureTodayBucket();
|
|
652
|
+
return this.#todaySummary.totalCost;
|
|
653
|
+
}
|
|
654
|
+
/** 今日 API 调用次数 */
|
|
655
|
+
get todayCallCount() {
|
|
656
|
+
this.#ensureTodayBucket();
|
|
657
|
+
return this.#todaySummary.totalCalls;
|
|
658
|
+
}
|
|
659
|
+
// -----------------------------------------------------------------------
|
|
660
|
+
// 预算检查
|
|
661
|
+
// -----------------------------------------------------------------------
|
|
662
|
+
/** 是否已超出预算 */
|
|
663
|
+
get isBudgetExceeded() {
|
|
664
|
+
if (this.#budgetLimit > 0 && this.todayTotalCost >= this.#budgetLimit) {
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
if (this.#tokenBudgetLimit > 0) {
|
|
668
|
+
const totalTokens = this.#todaySummary.totalPromptTokens + this.#todaySummary.totalCompletionTokens;
|
|
669
|
+
if (totalTokens >= this.#tokenBudgetLimit) return true;
|
|
670
|
+
}
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
/** 剩余预算(元),无限制时返回 Infinity */
|
|
674
|
+
get remainingBudget() {
|
|
675
|
+
if (this.#budgetLimit <= 0) return Infinity;
|
|
676
|
+
return Math.max(0, this.#budgetLimit - this.todayTotalCost);
|
|
677
|
+
}
|
|
678
|
+
// -----------------------------------------------------------------------
|
|
679
|
+
// 持久化
|
|
680
|
+
// -----------------------------------------------------------------------
|
|
681
|
+
/**
|
|
682
|
+
* 从磁盘加载历史成本数据。
|
|
683
|
+
* 启动时调用,用于恢复今日和历史数据。
|
|
684
|
+
*/
|
|
685
|
+
async load() {
|
|
686
|
+
const store = await this.#loadStore();
|
|
687
|
+
const today = getTodayStr();
|
|
688
|
+
if (store.daily[today]) {
|
|
689
|
+
this.#todaySummary = store.daily[today];
|
|
690
|
+
this.#todayDate = today;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* 将脏数据持久化到磁盘。
|
|
695
|
+
* 通常在每次 record() 后异步调用,或在会话结束时主动调用。
|
|
696
|
+
*/
|
|
697
|
+
async flush() {
|
|
698
|
+
if (!this.#dirty || this.#flushInProgress) return;
|
|
699
|
+
this.#flushInProgress = true;
|
|
700
|
+
this.#dirty = false;
|
|
701
|
+
try {
|
|
702
|
+
await this.#saveStore();
|
|
703
|
+
} finally {
|
|
704
|
+
this.#flushInProgress = false;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* 查询指定日期范围的成本汇总。
|
|
709
|
+
* @param startDate 起始日期(YYYY-MM-DD),包含
|
|
710
|
+
* @param endDate 截止日期(YYYY-MM-DD),包含,默认同 startDate
|
|
711
|
+
*/
|
|
712
|
+
async queryRange(startDate, endDate) {
|
|
713
|
+
const store = await this.#loadStore();
|
|
714
|
+
const end = endDate ?? startDate;
|
|
715
|
+
const result = [];
|
|
716
|
+
for (const [date, summary] of Object.entries(store.daily)) {
|
|
717
|
+
if (date >= startDate && date <= end) {
|
|
718
|
+
result.push(summary);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
result.sort((a, b) => a.date.localeCompare(b.date));
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
/** 重置会话(开始新会话,不影响日级累计) */
|
|
725
|
+
resetSession() {
|
|
726
|
+
this.#sessionId = generateSessionId();
|
|
727
|
+
this.#sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
728
|
+
this.#sessionRecords.length = 0;
|
|
729
|
+
}
|
|
730
|
+
// -----------------------------------------------------------------------
|
|
731
|
+
// 内部方法
|
|
732
|
+
// -----------------------------------------------------------------------
|
|
733
|
+
/** 确保今日的统计桶存在(处理跨日情况) */
|
|
734
|
+
#ensureTodayBucket() {
|
|
735
|
+
const today = getTodayStr();
|
|
736
|
+
if (today !== this.#todayDate) {
|
|
737
|
+
this.#todayDate = today;
|
|
738
|
+
this.#todaySummary = createEmptyDailySummary(today);
|
|
739
|
+
this.#dirty = true;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/** 将一条记录累加到日级汇总 */
|
|
743
|
+
#addToDaily(record) {
|
|
744
|
+
this.#todaySummary.totalPromptTokens += record.usage.promptTokens;
|
|
745
|
+
this.#todaySummary.totalCompletionTokens += record.usage.completionTokens;
|
|
746
|
+
this.#todaySummary.totalCachedTokens += record.usage.cachedPromptTokens ?? 0;
|
|
747
|
+
this.#todaySummary.totalCost += record.cost.totalCost;
|
|
748
|
+
this.#todaySummary.totalCalls += 1;
|
|
749
|
+
const modelKey = record.model;
|
|
750
|
+
if (!this.#todaySummary.byModel[modelKey]) {
|
|
751
|
+
this.#todaySummary.byModel[modelKey] = {
|
|
752
|
+
model: record.model,
|
|
753
|
+
totalPromptTokens: 0,
|
|
754
|
+
totalCompletionTokens: 0,
|
|
755
|
+
totalCachedTokens: 0,
|
|
756
|
+
totalCost: 0,
|
|
757
|
+
totalCalls: 0
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
const modelSummary = this.#todaySummary.byModel[modelKey];
|
|
761
|
+
modelSummary.totalPromptTokens += record.usage.promptTokens;
|
|
762
|
+
modelSummary.totalCompletionTokens += record.usage.completionTokens;
|
|
763
|
+
modelSummary.totalCachedTokens += record.usage.cachedPromptTokens ?? 0;
|
|
764
|
+
modelSummary.totalCost += record.cost.totalCost;
|
|
765
|
+
modelSummary.totalCalls += 1;
|
|
766
|
+
}
|
|
767
|
+
/** 预算超限检查 */
|
|
768
|
+
#checkBudget() {
|
|
769
|
+
if (!this.isBudgetExceeded) return;
|
|
770
|
+
this.#onBudgetExceeded?.(this);
|
|
771
|
+
}
|
|
772
|
+
/** 从磁盘加载成本存储 */
|
|
773
|
+
async #loadStore() {
|
|
774
|
+
const filePath = join2(this.#costDir, "history.json");
|
|
775
|
+
try {
|
|
776
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
777
|
+
return JSON.parse(raw);
|
|
778
|
+
} catch {
|
|
779
|
+
return { version: 1, daily: {} };
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/** 保存成本存储到磁盘 */
|
|
783
|
+
async #saveStore() {
|
|
784
|
+
const store = await this.#loadStore();
|
|
785
|
+
store.daily[this.#todayDate] = this.#todaySummary;
|
|
786
|
+
const cutoffDate = getDateNDaysAgo(90);
|
|
787
|
+
const datesToRemove = Object.keys(store.daily).filter(
|
|
788
|
+
(date) => date < cutoffDate
|
|
789
|
+
);
|
|
790
|
+
for (const date of datesToRemove) {
|
|
791
|
+
delete store.daily[date];
|
|
792
|
+
}
|
|
793
|
+
await mkdir2(this.#costDir, { recursive: true });
|
|
794
|
+
const filePath = join2(this.#costDir, "history.json");
|
|
795
|
+
await writeFile2(filePath, JSON.stringify(store, null, 2), "utf-8");
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
function generateSessionId() {
|
|
799
|
+
const ts = Date.now().toString(36);
|
|
800
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
801
|
+
return `${ts}-${rand}`;
|
|
802
|
+
}
|
|
803
|
+
function getTodayStr() {
|
|
804
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
805
|
+
}
|
|
806
|
+
function getDateNDaysAgo(n) {
|
|
807
|
+
const d = /* @__PURE__ */ new Date();
|
|
808
|
+
d.setDate(d.getDate() - n);
|
|
809
|
+
return d.toISOString().slice(0, 10);
|
|
810
|
+
}
|
|
811
|
+
function createEmptyDailySummary(date) {
|
|
812
|
+
return {
|
|
813
|
+
date,
|
|
814
|
+
totalPromptTokens: 0,
|
|
815
|
+
totalCompletionTokens: 0,
|
|
816
|
+
totalCachedTokens: 0,
|
|
817
|
+
totalCost: 0,
|
|
818
|
+
totalCalls: 0,
|
|
819
|
+
byModel: {}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
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)}`;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/ui/ToolCallBlock.tsx
|
|
830
|
+
import { Box as Box3, Text as Text4 } from "ink";
|
|
491
831
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
832
|
+
function formatArgsSummary(args) {
|
|
833
|
+
try {
|
|
834
|
+
const parsed = JSON.parse(args);
|
|
835
|
+
const lines = Object.entries(parsed).map(([key, value]) => {
|
|
836
|
+
const val = String(value);
|
|
837
|
+
const truncated = val.length > 80 ? val.slice(0, 77) + "..." : val;
|
|
838
|
+
return ` ${key}: ${truncated}`;
|
|
839
|
+
});
|
|
840
|
+
return lines.join("\n");
|
|
841
|
+
} catch {
|
|
842
|
+
const truncated = args.length > 120 ? args.slice(0, 117) + "..." : args;
|
|
843
|
+
return ` ${truncated}`;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function ToolCallBlock({ call, showPendingHint = true }) {
|
|
847
|
+
const argsDisplay = formatArgsSummary(call.arguments);
|
|
848
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: [
|
|
849
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
850
|
+
/* @__PURE__ */ jsxs3(Text4, { color: "#00ffff", bold: true, children: [
|
|
851
|
+
"\u{1F4E6} ",
|
|
852
|
+
call.name
|
|
853
|
+
] }),
|
|
854
|
+
/* @__PURE__ */ jsxs3(Text4, { color: "#555555", children: [
|
|
855
|
+
" ",
|
|
856
|
+
"\u2500".repeat(Math.max(1, 30 - call.name.length))
|
|
857
|
+
] })
|
|
858
|
+
] }),
|
|
859
|
+
/* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { color: "#888888", children: argsDisplay }) }),
|
|
860
|
+
showPendingHint && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, { color: "yellow", children: "\u23F3 \u7B49\u5F85\u6267\u884C\uFF08\u5DE5\u5177\u7CFB\u7EDF\u5C06\u5728\u7B2C08\u7AE0\u5B9E\u73B0\uFF09" }) })
|
|
861
|
+
] });
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/agent/message-builder.ts
|
|
865
|
+
function estimateMessageTokens(msg) {
|
|
866
|
+
let text = msg.content;
|
|
867
|
+
if (msg.toolCalls) {
|
|
868
|
+
for (const tc of msg.toolCalls) {
|
|
869
|
+
text += tc.name + tc.arguments;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return estimateTokens(text) + 10;
|
|
873
|
+
}
|
|
874
|
+
function trimMessages(messages, opts) {
|
|
875
|
+
const meta = getModelMeta(opts.model);
|
|
876
|
+
const maxInputTokens = meta.contextWindow - opts.reservedForOutput;
|
|
877
|
+
const systemTokens = estimateTokens(opts.systemPrompt);
|
|
878
|
+
let remaining = maxInputTokens - systemTokens;
|
|
879
|
+
const preserved = [];
|
|
880
|
+
let roundsPreserved = 0;
|
|
881
|
+
for (let i = messages.length - 1; i >= 0 && roundsPreserved < opts.preserveRecentRounds; i--) {
|
|
882
|
+
preserved.unshift(messages[i]);
|
|
883
|
+
if (messages[i].role === "user") {
|
|
884
|
+
roundsPreserved++;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
for (const msg of preserved) {
|
|
888
|
+
remaining -= estimateMessageTokens(msg);
|
|
889
|
+
}
|
|
890
|
+
if (remaining < 0) {
|
|
891
|
+
while (preserved.length > 1 && remaining < 0) {
|
|
892
|
+
const removed = preserved.shift();
|
|
893
|
+
remaining += estimateMessageTokens(removed);
|
|
894
|
+
}
|
|
895
|
+
return [preserved, true];
|
|
896
|
+
}
|
|
897
|
+
const olderMessages = messages.slice(0, messages.length - preserved.length);
|
|
898
|
+
const kept = [];
|
|
899
|
+
for (let i = olderMessages.length - 1; i >= 0; i--) {
|
|
900
|
+
const cost = estimateMessageTokens(olderMessages[i]);
|
|
901
|
+
if (remaining - cost < 0) break;
|
|
902
|
+
remaining -= cost;
|
|
903
|
+
kept.unshift(olderMessages[i]);
|
|
904
|
+
}
|
|
905
|
+
const result = [...kept, ...preserved];
|
|
906
|
+
const trimmed = result.length < messages.length;
|
|
907
|
+
return [result, trimmed];
|
|
908
|
+
}
|
|
909
|
+
function buildApiMessages(systemPrompt, history) {
|
|
910
|
+
return [
|
|
911
|
+
{ role: "system", content: systemPrompt },
|
|
912
|
+
...history
|
|
913
|
+
];
|
|
914
|
+
}
|
|
915
|
+
function formatUsageSummary(usage) {
|
|
916
|
+
const prompt = usage.promptTokens.toLocaleString();
|
|
917
|
+
const completion = usage.completionTokens.toLocaleString();
|
|
918
|
+
const total = (usage.promptTokens + usage.completionTokens).toLocaleString();
|
|
919
|
+
let summary = `\u{1F4E5} ${prompt} + \u{1F4E4} ${completion} = \u{1F4E6} ${total} tokens`;
|
|
920
|
+
if (usage.cachedPromptTokens && usage.cachedPromptTokens > 0) {
|
|
921
|
+
const cacheRate = (usage.cachedPromptTokens / usage.promptTokens * 100).toFixed(1);
|
|
922
|
+
summary += ` \u2502 \u{1F5C4}\uFE0F \u7F13\u5B58\u547D\u4E2D ${cacheRate}%`;
|
|
923
|
+
}
|
|
924
|
+
return summary;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/ui/AssistantMessage.tsx
|
|
928
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
929
|
+
function formatElapsed(ms) {
|
|
930
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
931
|
+
const seconds = (ms / 1e3).toFixed(1);
|
|
932
|
+
return `${seconds}s`;
|
|
933
|
+
}
|
|
934
|
+
function AssistantMessage({
|
|
935
|
+
content,
|
|
936
|
+
toolCalls,
|
|
937
|
+
isStreaming = false,
|
|
938
|
+
usage,
|
|
939
|
+
elapsed,
|
|
940
|
+
cost,
|
|
941
|
+
model
|
|
942
|
+
}) {
|
|
943
|
+
if (!content && (!toolCalls || toolCalls.length === 0) && !isStreaming) {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
|
|
947
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", children: [
|
|
948
|
+
/* @__PURE__ */ jsx4(Box4, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx4(Text5, { bold: true, color: "#ff00ff", children: "\u{1F916}" }) }),
|
|
949
|
+
/* @__PURE__ */ jsxs4(Box4, { flexGrow: 1, flexDirection: "column", children: [
|
|
950
|
+
content && /* @__PURE__ */ jsx4(Text5, { wrap: "wrap", children: content }),
|
|
951
|
+
isStreaming && !content && /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: "..." })
|
|
952
|
+
] })
|
|
953
|
+
] }),
|
|
954
|
+
toolCalls && toolCalls.length > 0 && /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: toolCalls.map((tc, i) => /* @__PURE__ */ jsx4(ToolCallBlock, { call: tc }, tc.id ?? i)) }),
|
|
955
|
+
!isStreaming && (usage || elapsed !== void 0) && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, marginLeft: 3, children: [
|
|
956
|
+
/* @__PURE__ */ jsx4(Text5, { color: "#555555", children: "\u2500".repeat(36) }),
|
|
957
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", gap: 2, children: [
|
|
958
|
+
cost !== void 0 && cost > 0 && /* @__PURE__ */ jsxs4(Text5, { color: "yellow", children: [
|
|
959
|
+
"\u{1F4B0} \u672C\u6B21 ",
|
|
960
|
+
formatMoney(cost)
|
|
961
|
+
] }),
|
|
962
|
+
elapsed !== void 0 && /* @__PURE__ */ jsxs4(Text5, { color: "cyan", children: [
|
|
963
|
+
"\u{1F550} ",
|
|
964
|
+
formatElapsed(elapsed)
|
|
965
|
+
] }),
|
|
966
|
+
usage && /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: formatUsageSummary(usage) })
|
|
967
|
+
] })
|
|
968
|
+
] })
|
|
969
|
+
] });
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/provider/registry.ts
|
|
973
|
+
var ProviderRegistry = class {
|
|
974
|
+
#factories = /* @__PURE__ */ new Map();
|
|
975
|
+
#instances = /* @__PURE__ */ new Map();
|
|
976
|
+
/** 注册一个 Provider 工厂 */
|
|
977
|
+
register(name, factory) {
|
|
978
|
+
this.#factories.set(name, factory);
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* 获取或创建一个 Provider 实例(单例)。
|
|
982
|
+
*
|
|
983
|
+
* 相同的 name + baseUrl + model 组合会复用同一实例。
|
|
984
|
+
* 如果 model 不在支持列表中,抛出 ModelNotSupportedError。
|
|
985
|
+
*/
|
|
986
|
+
get(name, config) {
|
|
987
|
+
if (!isSupportedModel(config.model)) {
|
|
988
|
+
throw new ModelNotSupportedError(config.model);
|
|
989
|
+
}
|
|
990
|
+
const cacheKey = `${name}:${config.baseUrl}:${config.model}`;
|
|
991
|
+
const cached = this.#instances.get(cacheKey);
|
|
992
|
+
if (cached) return cached;
|
|
993
|
+
const factory = this.#factories.get(name);
|
|
994
|
+
if (!factory) {
|
|
995
|
+
const available = [...this.#factories.keys()].join(", ");
|
|
996
|
+
throw new Error(`\u672A\u6CE8\u518C\u7684 Provider: "${name}"\u3002\u53EF\u7528: ${available}`);
|
|
997
|
+
}
|
|
998
|
+
const instance = factory(config);
|
|
999
|
+
this.#instances.set(cacheKey, instance);
|
|
1000
|
+
return instance;
|
|
1001
|
+
}
|
|
1002
|
+
/** 列出已注册的 Provider 名称 */
|
|
1003
|
+
list() {
|
|
1004
|
+
return [...this.#factories.keys()];
|
|
1005
|
+
}
|
|
1006
|
+
/** 清除缓存的实例(用于配置热重载) */
|
|
1007
|
+
clear() {
|
|
1008
|
+
this.#instances.clear();
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
var defaultRegistry = new ProviderRegistry();
|
|
1012
|
+
defaultRegistry.register("deepseek", (config) => {
|
|
1013
|
+
return new DeepSeekProvider({
|
|
1014
|
+
apiKey: config.apiKey,
|
|
1015
|
+
baseUrl: config.baseUrl,
|
|
1016
|
+
model: config.model
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
function createProvider(config) {
|
|
1020
|
+
return defaultRegistry.get(config.name, config);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/agent/extra-prompt.ts
|
|
1024
|
+
var EXTRA_PROMPT = [
|
|
1025
|
+
"\u3010\u7EC8\u7AEF\u8F93\u51FA\u7EA6\u675F\u3011",
|
|
1026
|
+
"",
|
|
1027
|
+
"\u4F60\u7684\u56DE\u590D\u5C06\u76F4\u63A5\u6E32\u67D3\u5728\u7528\u6237\u7EC8\u7AEF\u7684\u547D\u4EE4\u884C\u754C\u9762\u4E2D\uFF0C\u4E0D\u652F\u6301 Markdown \u6E32\u67D3\u3002",
|
|
1028
|
+
"\u8BF7\u4E25\u683C\u9075\u5B88\u4EE5\u4E0B\u683C\u5F0F\u89C4\u5219\u3002",
|
|
1029
|
+
"",
|
|
1030
|
+
"\u4E00\u3001\u7981\u6B62\u4F7F\u7528\u7684\u7B26\u53F7",
|
|
1031
|
+
" - \u4E0D\u8981\u4F7F\u7528 Markdown \u6807\u9898\u7B26\u53F7\uFF08\u5355\u72EC\u6216\u8FDE\u7EED\u7684\u4E95\u53F7\uFF09",
|
|
1032
|
+
" - \u4E0D\u8981\u4F7F\u7528\u7C97\u4F53\u6216\u659C\u4F53\u7B26\u53F7\uFF08\u661F\u53F7\u3001\u4E0B\u5212\u7EBF\uFF09",
|
|
1033
|
+
" - \u4E0D\u8981\u4F7F\u7528\u4EE3\u7801\u5757\u6807\u8BB0\uFF08\u8FDE\u7EED\u4E09\u4E2A\u53CD\u5F15\u53F7\uFF09",
|
|
1034
|
+
" - \u4E0D\u8981\u4F7F\u7528\u5757\u5F15\u7528\u7B26\u53F7\uFF08\u5927\u4E8E\u53F7\uFF09",
|
|
1035
|
+
" - \u4E0D\u8981\u4F7F\u7528\u5206\u9694\u7EBF\uFF08\u8FDE\u7EED\u4E09\u4E2A\u51CF\u53F7\u6216\u661F\u53F7\uFF09",
|
|
1036
|
+
" - \u4E0D\u8981\u4F7F\u7528\u884C\u5185\u4EE3\u7801\u6807\u8BB0\uFF08\u5355\u4E2A\u53CD\u5F15\u53F7\uFF09",
|
|
1037
|
+
"",
|
|
1038
|
+
"\u4E8C\u3001\u63A8\u8350\u7684\u7EC4\u7EC7\u65B9\u5F0F",
|
|
1039
|
+
' - \u7528\u7EAF\u6587\u5B57\u63CF\u8FF0\u5C42\u7EA7\uFF0C\u4F8B\u5982"\u7B2C\u4E00\u70B9"\u3001"\u7B2C\u4E8C\u70B9"\u66FF\u4EE3\u6807\u9898\u7B26\u53F7',
|
|
1040
|
+
" - \u7528 emoji \u7B26\u53F7\u505A\u89C6\u89C9\u6807\u8BB0\uFF08\u5982 \u{1F4CC} \u{1F4E6} \u{1F4DD} \u26A0\uFE0F \u7B49\uFF09",
|
|
1041
|
+
" - \u7528\u6570\u5B57\u5E8F\u53F7\uFF081. 2. 3.\uFF09\u7EC4\u7EC7\u5217\u8868",
|
|
1042
|
+
" - \u4EE3\u7801\u793A\u4F8B\u7528\u7F29\u8FDB\uFF08\u6BCF\u884C\u524D\u52A0 4 \u4E2A\u7A7A\u683C\uFF09\u66FF\u4EE3\u4EE3\u7801\u5757\u6807\u8BB0",
|
|
1043
|
+
" - \u6587\u4EF6\u8DEF\u5F84\u3001\u547D\u4EE4\u3001\u53D8\u91CF\u540D\u76F4\u63A5\u4E66\u5199\uFF0C\u4E0D\u8981\u7528\u4EFB\u4F55\u7B26\u53F7\u5305\u88F9",
|
|
1044
|
+
"",
|
|
1045
|
+
"\u4E09\u3001\u6B63\u786E\u683C\u5F0F\u793A\u4F8B",
|
|
1046
|
+
"",
|
|
1047
|
+
" \u7B2C\u4E00\u70B9\uFF1A\u4F18\u5316\u6570\u636E\u5E93\u67E5\u8BE2",
|
|
1048
|
+
" 1. \u6DFB\u52A0\u7D22\u5F15\u5230 user_id \u5B57\u6BB5",
|
|
1049
|
+
" 2. \u7528\u8FDE\u63A5\u6C60\u66FF\u4EE3\u77ED\u8FDE\u63A5",
|
|
1050
|
+
"",
|
|
1051
|
+
" \u4EE3\u7801\u793A\u4F8B\uFF1A",
|
|
1052
|
+
" const pool = new Pool({ max: 20 });",
|
|
1053
|
+
' await pool.query("SELECT * FROM users");',
|
|
1054
|
+
"",
|
|
1055
|
+
" \u8F93\u51FA\u63D0\u793A\uFF1A",
|
|
1056
|
+
" \u{1F4CC} \u4F18\u5316\u524D\uFF1A\u67E5\u8BE2\u8017\u65F6 3.2 \u79D2",
|
|
1057
|
+
"",
|
|
1058
|
+
"\u56DB\u3001\u9519\u8BEF\u683C\u5F0F\u793A\u4F8B\uFF08\u7981\u6B62\u8FD9\u6837\u5199\uFF09",
|
|
1059
|
+
"",
|
|
1060
|
+
" \u4E95\u53F7\u4E32 \u4F18\u5316\u6570\u636E\u5E93\u67E5\u8BE2\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
|
|
1061
|
+
" \u661F\u53F7 \u6DFB\u52A0\u7D22\u5F15\u5230 user_id \u5B57\u6BB5\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
|
|
1062
|
+
" \u4E09\u4E2A\u53CD\u5F15\u53F7\u5F00\u59CB\u6216\u7ED3\u675F\uFF08\u8FD9\u662F\u9519\u8BEF\u7684\uFF09",
|
|
1063
|
+
"",
|
|
1064
|
+
"\u6CE8\u610F\uFF1A\u4E0A\u8FF0\u793A\u4F8B\u4E2D\u7684\u9519\u8BEF\u5199\u6CD5\u4EC5\u4F5C\u4E3A\u8BF4\u660E\uFF0C\u4F60\u7684\u56DE\u590D\u4E2D\u4E0D\u8981\u51FA\u73B0\u8FD9\u4E9B\u683C\u5F0F\u3002"
|
|
1065
|
+
].join("\n");
|
|
1066
|
+
|
|
1067
|
+
// src/agent/system-prompt.ts
|
|
1068
|
+
function buildSystemPrompt(opts) {
|
|
1069
|
+
const sections = [];
|
|
1070
|
+
sections.push(`\u4F60\u662F dskcode\uFF0C\u4E00\u4E2A\u57FA\u4E8E DeepSeek \u7684 AI \u7F16\u7A0B\u52A9\u624B\uFF0C\u8FD0\u884C\u5728\u7528\u6237\u7EC8\u7AEF\u4E2D\u3002\u4F60\u7684\u804C\u8D23\u662F\u5E2E\u52A9\u5F00\u53D1\u8005\u7F16\u5199\u3001\u7406\u89E3\u3001\u8C03\u8BD5\u548C\u91CD\u6784\u4EE3\u7801\u3002
|
|
1071
|
+
|
|
1072
|
+
## \u6838\u5FC3\u539F\u5219
|
|
1073
|
+
- \u56DE\u7B54\u4F7F\u7528\u4E2D\u6587\uFF0C\u4EE3\u7801\u6807\u8BC6\u7B26\u4FDD\u6301\u82F1\u6587
|
|
1074
|
+
- \u63D0\u4F9B\u51C6\u786E\u3001\u5B9E\u7528\u3001\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE
|
|
1075
|
+
- \u5BF9\u4E0D\u786E\u5B9A\u7684\u5185\u5BB9\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E
|
|
1076
|
+
- \u4EE3\u7801\u793A\u4F8B\u4FDD\u6301\u7B80\u6D01\uFF0C\u9644\u5E26\u5FC5\u8981\u7684\u6CE8\u91CA`);
|
|
1077
|
+
sections.push(`## \u5F53\u524D\u6A21\u578B
|
|
1078
|
+
- \u6A21\u578B\uFF1A${opts.model}`);
|
|
1079
|
+
const now = /* @__PURE__ */ new Date();
|
|
1080
|
+
const dateStr = now.toLocaleDateString("zh-CN", {
|
|
1081
|
+
year: "numeric",
|
|
1082
|
+
month: "long",
|
|
1083
|
+
day: "numeric",
|
|
1084
|
+
weekday: "long"
|
|
1085
|
+
});
|
|
1086
|
+
const timeStr = now.toLocaleTimeString("zh-CN", {
|
|
1087
|
+
hour: "2-digit",
|
|
1088
|
+
minute: "2-digit"
|
|
1089
|
+
});
|
|
1090
|
+
sections.push(`## \u65F6\u95F4\u4E0A\u4E0B\u6587
|
|
1091
|
+
- \u5F53\u524D\u65E5\u671F\uFF1A${dateStr}
|
|
1092
|
+
- \u5F53\u524D\u65F6\u95F4\uFF1A${timeStr}
|
|
1093
|
+
- \u5DE5\u4F5C\u76EE\u5F55\uFF1A${opts.cwd}`);
|
|
1094
|
+
if (opts.tools && opts.tools.length > 0) {
|
|
1095
|
+
const toolLines = opts.tools.map((t) => {
|
|
1096
|
+
const paramInfo = t.parameters ? Object.keys(t.parameters.properties ?? {}).join(", ") : "";
|
|
1097
|
+
return `- **${t.name}**\uFF1A${t.description}${paramInfo ? `\uFF08\u53C2\u6570\uFF1A${paramInfo}\uFF09` : ""}`;
|
|
1098
|
+
}).join("\n");
|
|
1099
|
+
sections.push(`## \u53EF\u7528\u5DE5\u5177
|
|
1100
|
+
|
|
1101
|
+
\u4F60\u53EF\u4EE5\u901A\u8FC7\u5DE5\u5177\u8C03\u7528\u6267\u884C\u64CD\u4F5C\u3002\u5F53\u524D\u53EF\u7528\u7684\u5DE5\u5177\uFF1A
|
|
1102
|
+
|
|
1103
|
+
${toolLines}
|
|
1104
|
+
|
|
1105
|
+
\u8C03\u7528\u5DE5\u5177\u65F6\uFF0C\u8BF7\u4F7F\u7528\u6807\u51C6 function_call \u683C\u5F0F\u3002\u5DE5\u5177\u5C06\u7531\u7CFB\u7EDF\u6267\u884C\u5E76\u8FD4\u56DE\u7ED3\u679C\u3002`);
|
|
1106
|
+
}
|
|
1107
|
+
if (opts.projectContext) {
|
|
1108
|
+
sections.push(`## \u9879\u76EE\u4E0A\u4E0B\u6587
|
|
1109
|
+
|
|
1110
|
+
${opts.projectContext}`);
|
|
1111
|
+
}
|
|
1112
|
+
sections.push(`## \u884C\u4E3A\u7EA6\u675F
|
|
1113
|
+
- \u5982\u679C\u7528\u6237\u8BF7\u6C42\u4E0D\u660E\u786E\uFF0C\u8BF7\u4E3B\u52A8\u8BE2\u95EE\u6F84\u6E05
|
|
1114
|
+
- \u6D89\u53CA\u6587\u4EF6\u64CD\u4F5C\u65F6\uFF0C\u5148\u786E\u8BA4\u6587\u4EF6\u8DEF\u5F84
|
|
1115
|
+
- \u4E0D\u6267\u884C\u53EF\u80FD\u9020\u6210\u4E0D\u53EF\u9006\u635F\u5BB3\u7684\u64CD\u4F5C\uFF08\u5982 rm -rf\uFF09\u9664\u975E\u7528\u6237\u660E\u786E\u786E\u8BA4
|
|
1116
|
+
- \u5F53\u5DE5\u5177\u8C03\u7528\u8FD4\u56DE\u9519\u8BEF\u65F6\uFF0C\u5206\u6790\u539F\u56E0\u5E76\u5C1D\u8BD5\u4FEE\u590D`);
|
|
1117
|
+
sections.push(EXTRA_PROMPT);
|
|
1118
|
+
return sections.join("\n\n");
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/agent/index.ts
|
|
1122
|
+
var Session = class {
|
|
1123
|
+
#messages = [];
|
|
1124
|
+
#provider;
|
|
1125
|
+
#tools;
|
|
1126
|
+
#costTracker;
|
|
1127
|
+
#options;
|
|
1128
|
+
#abortController = new AbortController();
|
|
1129
|
+
constructor(provider, tools = [], costTracker, options) {
|
|
1130
|
+
this.#provider = provider;
|
|
1131
|
+
this.#tools = tools;
|
|
1132
|
+
this.#costTracker = costTracker ?? new CostTracker();
|
|
1133
|
+
this.#options = {
|
|
1134
|
+
cwd: options?.cwd ?? process.cwd(),
|
|
1135
|
+
maxToolRounds: options?.maxToolRounds ?? 20,
|
|
1136
|
+
reservedForOutput: options?.reservedForOutput ?? 4096,
|
|
1137
|
+
preserveRecentRounds: options?.preserveRecentRounds ?? 10,
|
|
1138
|
+
projectContext: options?.projectContext
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
// -------------------------------------------------------------------------
|
|
1142
|
+
// 公共只读属性
|
|
1143
|
+
// -------------------------------------------------------------------------
|
|
1144
|
+
get messages() {
|
|
1145
|
+
return this.#messages;
|
|
1146
|
+
}
|
|
1147
|
+
get accumulatedCost() {
|
|
1148
|
+
return this.#costTracker.sessionTotalCost;
|
|
1149
|
+
}
|
|
1150
|
+
get costTracker() {
|
|
1151
|
+
return this.#costTracker;
|
|
1152
|
+
}
|
|
1153
|
+
get model() {
|
|
1154
|
+
return this.#provider.model();
|
|
1155
|
+
}
|
|
1156
|
+
// -------------------------------------------------------------------------
|
|
1157
|
+
// 流式对话 — Agent 主循环
|
|
1158
|
+
// -------------------------------------------------------------------------
|
|
1159
|
+
/**
|
|
1160
|
+
* 执行一轮对话,以 AsyncGenerator 形式逐步 yield 事件。
|
|
1161
|
+
*
|
|
1162
|
+
* 调用方式:
|
|
1163
|
+
* ```ts
|
|
1164
|
+
* for await (const event of session.chat("你好")) {
|
|
1165
|
+
* switch (event.type) {
|
|
1166
|
+
* case "text_delta": // 追加文本
|
|
1167
|
+
* case "tool_calls": // 展示工具调用
|
|
1168
|
+
* case "usage": // 记录使用量
|
|
1169
|
+
* case "done": // 本轮完成
|
|
1170
|
+
* case "error": // 处理错误
|
|
1171
|
+
* }
|
|
1172
|
+
* }
|
|
1173
|
+
* ```
|
|
1174
|
+
*/
|
|
1175
|
+
async *chat(userInput) {
|
|
1176
|
+
this.#messages.push({ role: "user", content: userInput });
|
|
1177
|
+
const systemPrompt = this.#buildSystemPrompt();
|
|
1178
|
+
const [trimmed, wasTrimmed] = trimMessages(
|
|
1179
|
+
[...this.#messages],
|
|
1180
|
+
{
|
|
1181
|
+
model: this.#provider.model(),
|
|
1182
|
+
reservedForOutput: this.#options.reservedForOutput,
|
|
1183
|
+
systemPrompt,
|
|
1184
|
+
preserveRecentRounds: this.#options.preserveRecentRounds
|
|
1185
|
+
}
|
|
1186
|
+
);
|
|
1187
|
+
if (wasTrimmed) {
|
|
1188
|
+
}
|
|
1189
|
+
const apiMessages = buildApiMessages(systemPrompt, trimmed);
|
|
1190
|
+
const toolDefs = this.#buildToolDefinitions();
|
|
1191
|
+
const startTime = Date.now();
|
|
1192
|
+
try {
|
|
1193
|
+
const stream = this.#provider.chat(apiMessages, {
|
|
1194
|
+
signal: this.#abortController.signal
|
|
1195
|
+
// 将工具定义传给 provider(如支持 function calling)
|
|
1196
|
+
// 注意:当前 chat() 签名不含 tools 参数,
|
|
1197
|
+
// 工具定义在 system prompt 中描述,由模型通过文本方式请求
|
|
1198
|
+
});
|
|
1199
|
+
let accumulatedText = "";
|
|
1200
|
+
let lastUsage;
|
|
1201
|
+
let lastToolCalls;
|
|
1202
|
+
let lastFinishReason = null;
|
|
1203
|
+
for await (const chunk of stream) {
|
|
1204
|
+
if (chunk.content) {
|
|
1205
|
+
accumulatedText += chunk.content;
|
|
1206
|
+
yield { type: "text_delta", content: chunk.content };
|
|
1207
|
+
}
|
|
1208
|
+
if (chunk.toolCalls && chunk.toolCalls.length > 0) {
|
|
1209
|
+
lastToolCalls = chunk.toolCalls;
|
|
1210
|
+
}
|
|
1211
|
+
if (chunk.usage) {
|
|
1212
|
+
lastUsage = chunk.usage;
|
|
1213
|
+
}
|
|
1214
|
+
if (chunk.finishReason) {
|
|
1215
|
+
lastFinishReason = chunk.finishReason;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (lastToolCalls && lastToolCalls.length > 0) {
|
|
1219
|
+
yield { type: "tool_calls", calls: lastToolCalls };
|
|
1220
|
+
}
|
|
1221
|
+
if (lastUsage) {
|
|
1222
|
+
const modelId = this.#provider.model();
|
|
1223
|
+
const costInfo = this.#costTracker.record(lastUsage, modelId);
|
|
1224
|
+
yield { type: "usage", usage: lastUsage, model: modelId };
|
|
1225
|
+
}
|
|
1226
|
+
const assistantMsg = {
|
|
1227
|
+
role: "assistant",
|
|
1228
|
+
content: accumulatedText
|
|
1229
|
+
};
|
|
1230
|
+
if (lastToolCalls && lastToolCalls.length > 0) {
|
|
1231
|
+
assistantMsg.toolCalls = lastToolCalls;
|
|
1232
|
+
}
|
|
1233
|
+
this.#messages.push(assistantMsg);
|
|
1234
|
+
if (lastToolCalls && lastToolCalls.length > 0) {
|
|
1235
|
+
for (const tc of lastToolCalls) {
|
|
1236
|
+
this.#messages.push({
|
|
1237
|
+
role: "tool",
|
|
1238
|
+
content: `\u26A0 \u5DE5\u5177 "${tc.name}" \u7B49\u5F85\u6267\u884C\uFF08\u5DE5\u5177\u7CFB\u7EDF\u5C06\u5728\u7B2C08\u7AE0\u5B9E\u73B0\uFF09`,
|
|
1239
|
+
toolCallId: tc.id,
|
|
1240
|
+
name: tc.name
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
const elapsed = Date.now() - startTime;
|
|
1245
|
+
yield { type: "done", elapsed };
|
|
1246
|
+
} catch (err) {
|
|
1247
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
yield {
|
|
1254
|
+
type: "error",
|
|
1255
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
// -------------------------------------------------------------------------
|
|
1260
|
+
// 会话管理
|
|
1261
|
+
// -------------------------------------------------------------------------
|
|
1262
|
+
/** 取消正在进行的流式请求 */
|
|
1263
|
+
abort() {
|
|
1264
|
+
this.#abortController.abort();
|
|
1265
|
+
}
|
|
1266
|
+
/** 重置会话历史(保留 provider/tools 配置,重置成本追踪) */
|
|
1267
|
+
reset() {
|
|
1268
|
+
this.#messages.length = 0;
|
|
1269
|
+
this.#costTracker.resetSession();
|
|
1270
|
+
}
|
|
1271
|
+
// -------------------------------------------------------------------------
|
|
1272
|
+
// 内部方法
|
|
1273
|
+
// -------------------------------------------------------------------------
|
|
1274
|
+
/** 构建系统提示词 */
|
|
1275
|
+
#buildSystemPrompt() {
|
|
1276
|
+
const toolDescs = this.#tools.map((t) => ({
|
|
1277
|
+
name: t.name,
|
|
1278
|
+
description: t.description,
|
|
1279
|
+
parameters: t.parameters
|
|
1280
|
+
}));
|
|
1281
|
+
const opts = {
|
|
1282
|
+
model: this.#provider.model(),
|
|
1283
|
+
tools: toolDescs.length > 0 ? toolDescs : void 0,
|
|
1284
|
+
projectContext: this.#options.projectContext ?? void 0,
|
|
1285
|
+
cwd: this.#options.cwd
|
|
1286
|
+
};
|
|
1287
|
+
return buildSystemPrompt(opts);
|
|
1288
|
+
}
|
|
1289
|
+
/** 将注册的工具转为 ToolDefinition 格式(预留给 function calling) */
|
|
1290
|
+
#buildToolDefinitions() {
|
|
1291
|
+
return this.#tools.map((t) => ({
|
|
1292
|
+
type: "function",
|
|
1293
|
+
function: {
|
|
1294
|
+
name: t.name,
|
|
1295
|
+
description: t.description,
|
|
1296
|
+
parameters: t.parameters
|
|
1297
|
+
}
|
|
1298
|
+
}));
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
// src/utils/gradient.ts
|
|
1303
|
+
var IDLE_GRADIENT_STOPS = [
|
|
1304
|
+
[0, 255, 65],
|
|
1305
|
+
// #00ff41
|
|
1306
|
+
[0, 255, 255],
|
|
1307
|
+
// #00ffff
|
|
1308
|
+
[189, 147, 249]
|
|
1309
|
+
// #bd93f9
|
|
1310
|
+
];
|
|
1311
|
+
var STREAMING_GRADIENT_STOPS = [
|
|
1312
|
+
[255, 191, 0],
|
|
1313
|
+
// #ffbf00 琥珀金
|
|
1314
|
+
[210, 140, 60],
|
|
1315
|
+
// #d28c3c 焦糖
|
|
1316
|
+
[175, 70, 40]
|
|
1317
|
+
// #af4628 棕红
|
|
1318
|
+
];
|
|
1319
|
+
var GRADIENT_ANIMATION = {
|
|
1320
|
+
/** 空闲占位符动画:每帧相位步进(值越大流速越快) */
|
|
1321
|
+
idlePhaseStep: 0.06,
|
|
1322
|
+
/** 空闲占位符动画:帧间隔(ms) */
|
|
1323
|
+
idleInterval: 40,
|
|
1324
|
+
/** 流式占位符动画:每帧相位步进 */
|
|
1325
|
+
streamingPhaseStep: 0.05,
|
|
1326
|
+
/** 流式占位符动画:帧间隔(ms) */
|
|
1327
|
+
streamingInterval: 40
|
|
1328
|
+
};
|
|
1329
|
+
var SKIP_CHARS = /* @__PURE__ */ new Set([" ", "~", ".", "\u3002", "\uFF01", "\u{1F447}"]);
|
|
1330
|
+
function lerpStopsToHex(t, stops) {
|
|
1331
|
+
const len = stops.length;
|
|
1332
|
+
const segment = t * (len - 1);
|
|
1333
|
+
const idx = Math.min(Math.floor(segment), len - 2);
|
|
1334
|
+
const frac = segment - idx;
|
|
1335
|
+
const stop = stops[idx];
|
|
1336
|
+
const nextStop = stops[idx + 1];
|
|
1337
|
+
if (!stop || !nextStop) return "#808080";
|
|
1338
|
+
const r = Math.round(stop[0] + (nextStop[0] - stop[0]) * frac);
|
|
1339
|
+
const g = Math.round(stop[1] + (nextStop[1] - stop[1]) * frac);
|
|
1340
|
+
const b = Math.round(stop[2] + (nextStop[2] - stop[2]) * frac);
|
|
1341
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
1342
|
+
}
|
|
1343
|
+
function getGradientColors(text, phase, stops) {
|
|
1344
|
+
const len = text.length;
|
|
1345
|
+
if (len === 0) return [];
|
|
1346
|
+
return text.split("").map((ch, i) => {
|
|
1347
|
+
if (SKIP_CHARS.has(ch)) return "";
|
|
1348
|
+
const t = (i / (len - 1) + phase) % 1;
|
|
1349
|
+
return lerpStopsToHex(t, stops);
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/ui/ChatSession.tsx
|
|
1354
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
492
1355
|
var commandRegistry = /* @__PURE__ */ new Map();
|
|
493
1356
|
function registerCommand(name, cmd) {
|
|
494
1357
|
commandRegistry.set(name, cmd);
|
|
@@ -510,24 +1373,89 @@ registerCommand("/help", {
|
|
|
510
1373
|
}
|
|
511
1374
|
});
|
|
512
1375
|
registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
|
|
513
|
-
registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.
|
|
1376
|
+
registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.1.10" }) });
|
|
514
1377
|
registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
|
|
515
1378
|
registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
|
|
516
|
-
|
|
1379
|
+
var STREAMING_PLACEHOLDERS = [
|
|
1380
|
+
"\u8BA9\u5B50\u5F39\u98DE\u4E00\u4F1A\u513F...",
|
|
1381
|
+
"\u9A6C\u4E0A\u5C31\u597D...",
|
|
1382
|
+
"\u6B63\u5728\u618B\u5927\u62DB...",
|
|
1383
|
+
"\u7A0D\u7B49\u4E00\u4E0B\u4E0B~",
|
|
1384
|
+
"\u7801\u5B57\u4E2D...",
|
|
1385
|
+
"\u8111\u5B50\u8F6C\u5F97\u98DE\u5FEB..."
|
|
1386
|
+
];
|
|
1387
|
+
var IDLE_PLACEHOLDERS = [
|
|
1388
|
+
"\u60F3\u5E72\u5565\uFF1F\u76F4\u63A5\u8BF4~",
|
|
1389
|
+
"\u6765\u5427\uFF0C\u5429\u5490\u70B9\u5565",
|
|
1390
|
+
"\u968F\u65F6\u5F85\u547D...",
|
|
1391
|
+
"\u6233\u8FD9\u91CC\u5F00\u804A \u{1F447}",
|
|
1392
|
+
"\u7B49\u4F60\u5F00\u53E3...",
|
|
1393
|
+
"\u5C3D\u7BA1\u4F7F\u5524~"
|
|
1394
|
+
];
|
|
1395
|
+
function pickRandom(arr) {
|
|
1396
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
1397
|
+
}
|
|
1398
|
+
function ChatSession({
|
|
1399
|
+
providerCount,
|
|
1400
|
+
toolCount,
|
|
1401
|
+
verbose,
|
|
1402
|
+
apiKey,
|
|
1403
|
+
baseUrl,
|
|
1404
|
+
costTracker: externalCostTracker,
|
|
1405
|
+
model = "deepseek-v4-flash",
|
|
1406
|
+
onLaunchGame,
|
|
1407
|
+
onLaunchStock
|
|
1408
|
+
}) {
|
|
1409
|
+
const termWidth = typeof process.stdout.columns === "number" ? process.stdout.columns : 80;
|
|
1410
|
+
const dividerWidth = Math.max(termWidth - 2, 1);
|
|
517
1411
|
const [offset, setOffset] = useState3(0);
|
|
518
|
-
const [
|
|
1412
|
+
const [displayMessages, setDisplayMessages] = useState3([]);
|
|
519
1413
|
const [input, setInput] = useState3("");
|
|
520
1414
|
const [balance, setBalance] = useState3(null);
|
|
521
1415
|
const [balanceLoading, setBalanceLoading] = useState3(false);
|
|
522
|
-
const
|
|
1416
|
+
const [todayCost, setTodayCost] = useState3(null);
|
|
1417
|
+
const [isStreaming, setIsStreaming] = useState3(false);
|
|
1418
|
+
const [streamingPlaceholder, setStreamingPlaceholder] = useState3("");
|
|
1419
|
+
const [idlePlaceholder, setIdlePlaceholder] = useState3(() => pickRandom(IDLE_PLACEHOLDERS));
|
|
1420
|
+
const [gradientColors, setGradientColors] = useState3([]);
|
|
1421
|
+
const gradientPhaseRef = useRef2(0);
|
|
1422
|
+
const [streamingGradientColors, setStreamingGradientColors] = useState3([]);
|
|
1423
|
+
const streamingPhaseRef = useRef2(0);
|
|
1424
|
+
const [currentContent, setCurrentContent] = useState3("");
|
|
1425
|
+
const [currentToolCalls, setCurrentToolCalls] = useState3([]);
|
|
1426
|
+
const [currentUsage, setCurrentUsage] = useState3(void 0);
|
|
1427
|
+
const [currentElapsed, setCurrentElapsed] = useState3(void 0);
|
|
1428
|
+
const [currentCost, setCurrentCost] = useState3(void 0);
|
|
1429
|
+
const [currentModel, setCurrentModel] = useState3(void 0);
|
|
1430
|
+
const [streamError, setStreamError] = useState3(void 0);
|
|
1431
|
+
const sessionRef = useRef2(null);
|
|
1432
|
+
const abortRef = useRef2(null);
|
|
1433
|
+
const currentContentRef = useRef2("");
|
|
1434
|
+
const currentToolCallsRef = useRef2([]);
|
|
1435
|
+
const currentUsageRef = useRef2(void 0);
|
|
1436
|
+
const currentElapsedRef = useRef2(void 0);
|
|
1437
|
+
const currentCostRef = useRef2(void 0);
|
|
1438
|
+
const currentModelRef = useRef2(void 0);
|
|
1439
|
+
const streamErrorRef = useRef2(void 0);
|
|
1440
|
+
const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => {
|
|
1441
|
+
process.exit(0);
|
|
1442
|
+
});
|
|
523
1443
|
useInput(
|
|
524
1444
|
useCallback2(
|
|
525
|
-
(
|
|
526
|
-
if (
|
|
527
|
-
|
|
1445
|
+
(_input, key) => {
|
|
1446
|
+
if (key.ctrl && _input === "c") {
|
|
1447
|
+
if (isStreaming) {
|
|
1448
|
+
abortRef.current?.abort();
|
|
1449
|
+
} else {
|
|
1450
|
+
handleCtrlC();
|
|
1451
|
+
}
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
if (!input && !isStreaming && _input) {
|
|
1455
|
+
setInput(_input);
|
|
528
1456
|
}
|
|
529
1457
|
},
|
|
530
|
-
[handleCtrlC]
|
|
1458
|
+
[isStreaming, handleCtrlC, input]
|
|
531
1459
|
)
|
|
532
1460
|
);
|
|
533
1461
|
useEffect3(() => {
|
|
@@ -536,12 +1464,29 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
|
|
|
536
1464
|
}, 500);
|
|
537
1465
|
return () => clearInterval(timer);
|
|
538
1466
|
}, []);
|
|
1467
|
+
useEffect3(() => {
|
|
1468
|
+
if (!apiKey || !baseUrl) return;
|
|
1469
|
+
const provider = createProvider({
|
|
1470
|
+
name: "deepseek",
|
|
1471
|
+
apiKey,
|
|
1472
|
+
baseUrl,
|
|
1473
|
+
model
|
|
1474
|
+
});
|
|
1475
|
+
const tracker = externalCostTracker ?? new CostTracker();
|
|
1476
|
+
const session = new Session(provider, [], tracker, {
|
|
1477
|
+
cwd: process.cwd()
|
|
1478
|
+
});
|
|
1479
|
+
sessionRef.current = session;
|
|
1480
|
+
return () => {
|
|
1481
|
+
sessionRef.current = null;
|
|
1482
|
+
};
|
|
1483
|
+
}, [apiKey, baseUrl, model, externalCostTracker]);
|
|
539
1484
|
useEffect3(() => {
|
|
540
1485
|
if (!apiKey || !baseUrl) return;
|
|
541
1486
|
let cancelled = false;
|
|
542
1487
|
setBalanceLoading(true);
|
|
543
|
-
import("./deepseek-
|
|
544
|
-
const provider = new
|
|
1488
|
+
import("./deepseek-OIJOG3N5.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
|
|
1489
|
+
const provider = new DeepSeekProvider2({
|
|
545
1490
|
apiKey,
|
|
546
1491
|
baseUrl,
|
|
547
1492
|
model: "deepseek-v4-flash"
|
|
@@ -561,7 +1506,51 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
|
|
|
561
1506
|
cancelled = true;
|
|
562
1507
|
};
|
|
563
1508
|
}, [apiKey, baseUrl]);
|
|
564
|
-
|
|
1509
|
+
useEffect3(() => {
|
|
1510
|
+
if (!externalCostTracker) return;
|
|
1511
|
+
let cancelled = false;
|
|
1512
|
+
let timer;
|
|
1513
|
+
const refresh = () => {
|
|
1514
|
+
setTodayCost(externalCostTracker.todayTotalCost);
|
|
1515
|
+
};
|
|
1516
|
+
externalCostTracker.load().then(() => {
|
|
1517
|
+
if (cancelled) return;
|
|
1518
|
+
refresh();
|
|
1519
|
+
timer = setInterval(refresh, 5e3);
|
|
1520
|
+
}).catch(() => {
|
|
1521
|
+
});
|
|
1522
|
+
return () => {
|
|
1523
|
+
cancelled = true;
|
|
1524
|
+
if (timer) clearInterval(timer);
|
|
1525
|
+
};
|
|
1526
|
+
}, [externalCostTracker]);
|
|
1527
|
+
useEffect3(() => {
|
|
1528
|
+
if (isStreaming || !idlePlaceholder) {
|
|
1529
|
+
setGradientColors([]);
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
gradientPhaseRef.current = 0;
|
|
1533
|
+
setGradientColors(getGradientColors(idlePlaceholder, 0, IDLE_GRADIENT_STOPS));
|
|
1534
|
+
const interval = setInterval(() => {
|
|
1535
|
+
gradientPhaseRef.current = (gradientPhaseRef.current + GRADIENT_ANIMATION.idlePhaseStep) % 1;
|
|
1536
|
+
setGradientColors(getGradientColors(idlePlaceholder, gradientPhaseRef.current, IDLE_GRADIENT_STOPS));
|
|
1537
|
+
}, GRADIENT_ANIMATION.idleInterval);
|
|
1538
|
+
return () => clearInterval(interval);
|
|
1539
|
+
}, [isStreaming, idlePlaceholder]);
|
|
1540
|
+
useEffect3(() => {
|
|
1541
|
+
if (!isStreaming || !streamingPlaceholder) {
|
|
1542
|
+
setStreamingGradientColors([]);
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
streamingPhaseRef.current = 0;
|
|
1546
|
+
setStreamingGradientColors(getGradientColors(streamingPlaceholder, 0, STREAMING_GRADIENT_STOPS));
|
|
1547
|
+
const interval = setInterval(() => {
|
|
1548
|
+
streamingPhaseRef.current = (streamingPhaseRef.current + GRADIENT_ANIMATION.streamingPhaseStep) % 1;
|
|
1549
|
+
setStreamingGradientColors(getGradientColors(streamingPlaceholder, streamingPhaseRef.current, STREAMING_GRADIENT_STOPS));
|
|
1550
|
+
}, GRADIENT_ANIMATION.streamingInterval);
|
|
1551
|
+
return () => clearInterval(interval);
|
|
1552
|
+
}, [isStreaming, streamingPlaceholder]);
|
|
1553
|
+
const handleSubmit = useCallback2(async (value) => {
|
|
565
1554
|
const trimmed = value.trim();
|
|
566
1555
|
if (!trimmed) return;
|
|
567
1556
|
if (trimmed.startsWith("/")) {
|
|
@@ -573,8 +1562,9 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
|
|
|
573
1562
|
process.exit(0);
|
|
574
1563
|
return;
|
|
575
1564
|
case "clear":
|
|
576
|
-
|
|
1565
|
+
setDisplayMessages([]);
|
|
577
1566
|
setInput("");
|
|
1567
|
+
sessionRef.current?.reset();
|
|
578
1568
|
return;
|
|
579
1569
|
case "navigate":
|
|
580
1570
|
setInput("");
|
|
@@ -585,7 +1575,7 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
|
|
|
585
1575
|
}
|
|
586
1576
|
return;
|
|
587
1577
|
case "text":
|
|
588
|
-
|
|
1578
|
+
setDisplayMessages((prev) => [
|
|
589
1579
|
...prev,
|
|
590
1580
|
{ role: "user", content: trimmed },
|
|
591
1581
|
{ role: "assistant", content: result.content }
|
|
@@ -594,7 +1584,7 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
|
|
|
594
1584
|
return;
|
|
595
1585
|
}
|
|
596
1586
|
}
|
|
597
|
-
|
|
1587
|
+
setDisplayMessages((prev) => [
|
|
598
1588
|
...prev,
|
|
599
1589
|
{ role: "user", content: trimmed },
|
|
600
1590
|
{ role: "assistant", content: `\u672A\u77E5\u547D\u4EE4\uFF1A${trimmed}\u3002\u8F93\u5165 /help \u67E5\u770B\u3002` }
|
|
@@ -602,64 +1592,220 @@ function ChatSession({ providerCount, toolCount, verbose, apiKey, baseUrl, onLau
|
|
|
602
1592
|
setInput("");
|
|
603
1593
|
return;
|
|
604
1594
|
}
|
|
605
|
-
|
|
1595
|
+
if (!sessionRef.current) {
|
|
1596
|
+
setDisplayMessages((prev) => [
|
|
1597
|
+
...prev,
|
|
1598
|
+
{ role: "user", content: trimmed },
|
|
1599
|
+
{ role: "assistant", content: "\u26A0 \u65E0\u6CD5\u8FDE\u63A5\u5230 Provider\u3002\u8BF7\u68C0\u67E5 API Key \u548C\u7F51\u7EDC\u914D\u7F6E\u3002" }
|
|
1600
|
+
]);
|
|
1601
|
+
setInput("");
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
setDisplayMessages((prev) => [
|
|
606
1605
|
...prev,
|
|
607
|
-
{ role: "user", content: trimmed }
|
|
608
|
-
{ role: "assistant", content: "dskcode AI \u2014 \u5F85\u5B9E\u73B0\uFF08\u7B2C07\u7AE0\uFF09\u3002\u5F53\u524D\u4E3A CLI \u6846\u67B6\u6F14\u793A\u6A21\u5F0F\u3002" }
|
|
1606
|
+
{ role: "user", content: trimmed }
|
|
609
1607
|
]);
|
|
610
1608
|
setInput("");
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1609
|
+
setIsStreaming(true);
|
|
1610
|
+
setStreamingPlaceholder(pickRandom(STREAMING_PLACEHOLDERS));
|
|
1611
|
+
setCurrentContent("");
|
|
1612
|
+
setCurrentToolCalls([]);
|
|
1613
|
+
setCurrentUsage(void 0);
|
|
1614
|
+
setCurrentElapsed(void 0);
|
|
1615
|
+
setCurrentCost(void 0);
|
|
1616
|
+
setCurrentModel(void 0);
|
|
1617
|
+
setStreamError(void 0);
|
|
1618
|
+
currentContentRef.current = "";
|
|
1619
|
+
currentToolCallsRef.current = [];
|
|
1620
|
+
currentUsageRef.current = void 0;
|
|
1621
|
+
currentElapsedRef.current = void 0;
|
|
1622
|
+
currentCostRef.current = void 0;
|
|
1623
|
+
currentModelRef.current = void 0;
|
|
1624
|
+
streamErrorRef.current = void 0;
|
|
1625
|
+
const session = sessionRef.current;
|
|
1626
|
+
const abortController = new AbortController();
|
|
1627
|
+
abortRef.current = abortController;
|
|
1628
|
+
try {
|
|
1629
|
+
for await (const event of session.chat(trimmed)) {
|
|
1630
|
+
if (abortController.signal.aborted) break;
|
|
1631
|
+
switch (event.type) {
|
|
1632
|
+
case "text_delta":
|
|
1633
|
+
setCurrentContent((prev) => {
|
|
1634
|
+
const next = prev + event.content;
|
|
1635
|
+
currentContentRef.current = next;
|
|
1636
|
+
return next;
|
|
1637
|
+
});
|
|
1638
|
+
break;
|
|
1639
|
+
case "tool_calls":
|
|
1640
|
+
setCurrentToolCalls((prev) => {
|
|
1641
|
+
const next = [...prev, ...event.calls];
|
|
1642
|
+
currentToolCallsRef.current = next;
|
|
1643
|
+
return next;
|
|
1644
|
+
});
|
|
1645
|
+
break;
|
|
1646
|
+
case "usage":
|
|
1647
|
+
setCurrentUsage(event.usage);
|
|
1648
|
+
setCurrentModel(event.model);
|
|
1649
|
+
currentUsageRef.current = event.usage;
|
|
1650
|
+
currentModelRef.current = event.model;
|
|
1651
|
+
break;
|
|
1652
|
+
case "done":
|
|
1653
|
+
setCurrentElapsed(event.elapsed);
|
|
1654
|
+
currentElapsedRef.current = event.elapsed;
|
|
1655
|
+
break;
|
|
1656
|
+
case "error":
|
|
1657
|
+
setStreamError(event.error.message);
|
|
1658
|
+
streamErrorRef.current = event.error.message;
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
} catch (err) {
|
|
1663
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1664
|
+
setStreamError(msg);
|
|
1665
|
+
streamErrorRef.current = msg;
|
|
1666
|
+
} finally {
|
|
1667
|
+
setIsStreaming(false);
|
|
1668
|
+
setIdlePlaceholder(pickRandom(IDLE_PLACEHOLDERS));
|
|
1669
|
+
abortRef.current = null;
|
|
1670
|
+
const finContent = currentContentRef.current;
|
|
1671
|
+
const finToolCalls = currentToolCallsRef.current.length > 0 ? currentToolCallsRef.current : void 0;
|
|
1672
|
+
const finStreamError = streamErrorRef.current;
|
|
1673
|
+
if (finContent || finToolCalls || finStreamError) {
|
|
1674
|
+
const completed = {
|
|
1675
|
+
content: finStreamError ? `\u26A0 \u8BF7\u6C42\u51FA\u9519\uFF1A${finStreamError}` : finContent || "",
|
|
1676
|
+
toolCalls: finToolCalls,
|
|
1677
|
+
usage: currentUsageRef.current,
|
|
1678
|
+
elapsed: currentElapsedRef.current,
|
|
1679
|
+
cost: currentCostRef.current,
|
|
1680
|
+
model: currentModelRef.current
|
|
1681
|
+
};
|
|
1682
|
+
setDisplayMessages((prev) => [
|
|
1683
|
+
...prev,
|
|
1684
|
+
{
|
|
1685
|
+
role: "assistant",
|
|
1686
|
+
content: completed.content,
|
|
1687
|
+
assistantDetail: completed
|
|
1688
|
+
}
|
|
1689
|
+
]);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls]);
|
|
1693
|
+
useEffect3(() => {
|
|
1694
|
+
if (!isStreaming && externalCostTracker) {
|
|
1695
|
+
setTodayCost(externalCostTracker.todayTotalCost);
|
|
1696
|
+
}
|
|
1697
|
+
}, [isStreaming, externalCostTracker]);
|
|
1698
|
+
useEffect3(() => {
|
|
1699
|
+
if (currentUsage && currentModel && !currentCost) {
|
|
1700
|
+
import("./models-NQGNKB4T.js").then(({ calculateCost: calculateCost2 }) => {
|
|
1701
|
+
const cost = calculateCost2(currentUsage, currentModel);
|
|
1702
|
+
setCurrentCost(cost.totalCost);
|
|
1703
|
+
currentCostRef.current = cost.totalCost;
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
}, [currentUsage, currentModel, currentCost]);
|
|
1707
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
|
|
1708
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", marginBottom: 1, children: [
|
|
1709
|
+
/* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
|
|
615
1710
|
const colorIndex = (i + offset) % CYBER_PALETTE.length;
|
|
616
|
-
return /* @__PURE__ */
|
|
1711
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
|
|
617
1712
|
}) }),
|
|
618
|
-
/* @__PURE__ */
|
|
619
|
-
/* @__PURE__ */
|
|
1713
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", justifyContent: "center", children: [
|
|
1714
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "#00ff41", children: [
|
|
620
1715
|
" \u2714 ",
|
|
621
1716
|
"\u5DF2\u52A0\u8F7D ",
|
|
622
1717
|
providerCount,
|
|
623
1718
|
" \u4E2A Provider"
|
|
624
1719
|
] }),
|
|
625
|
-
/* @__PURE__ */
|
|
1720
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
|
|
626
1721
|
" \u2139 ",
|
|
627
1722
|
"\u5DF2\u5C31\u7EEA ",
|
|
628
1723
|
toolCount,
|
|
629
1724
|
" \u4E2A\u5DE5\u5177"
|
|
630
1725
|
] }),
|
|
631
|
-
|
|
1726
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
|
|
1727
|
+
" \u{1F527} \u6A21\u578B ",
|
|
1728
|
+
model
|
|
1729
|
+
] }),
|
|
1730
|
+
verbose ? /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
|
|
632
1731
|
] }),
|
|
633
|
-
/* @__PURE__ */
|
|
634
|
-
"\
|
|
635
|
-
|
|
636
|
-
|
|
1732
|
+
/* @__PURE__ */ jsxs5(Box5, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "flex-end", children: [
|
|
1733
|
+
balanceLoading && balance === null ? /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: " \u23F3 \u67E5\u8BE2\u4F59\u989D..." }) : balance !== null ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
|
|
1734
|
+
/* @__PURE__ */ jsx5(Text6, { color: "yellow", children: "\u{1F4B0} " }),
|
|
1735
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "yellow", children: [
|
|
1736
|
+
"\u4F59\u989D \xA5",
|
|
1737
|
+
balance.toFixed(2)
|
|
1738
|
+
] })
|
|
1739
|
+
] }) : null,
|
|
1740
|
+
todayCost !== null ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
|
|
1741
|
+
/* @__PURE__ */ jsx5(Text6, { color: "cyan", children: "\u{1F4CA} " }),
|
|
1742
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
|
|
1743
|
+
"\u4ECA\u65E5 \xA5",
|
|
1744
|
+
formatMoney(todayCost).replace("\xA5", "")
|
|
1745
|
+
] })
|
|
1746
|
+
] }) : null
|
|
1747
|
+
] })
|
|
637
1748
|
] }),
|
|
638
|
-
/* @__PURE__ */
|
|
639
|
-
/* @__PURE__ */
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1749
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
1750
|
+
/* @__PURE__ */ jsx5(Static, { items: displayMessages, children: (msg, i) => {
|
|
1751
|
+
if (msg.role === "user") {
|
|
1752
|
+
return /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
1753
|
+
/* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u{1F464}" }) }),
|
|
1754
|
+
/* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: /* @__PURE__ */ jsx5(Text6, { wrap: "wrap", children: msg.content }) })
|
|
1755
|
+
] }, i);
|
|
1756
|
+
}
|
|
1757
|
+
const detail = msg.assistantDetail;
|
|
1758
|
+
return /* @__PURE__ */ jsx5(
|
|
1759
|
+
AssistantMessage,
|
|
1760
|
+
{
|
|
1761
|
+
content: msg.content,
|
|
1762
|
+
toolCalls: detail?.toolCalls,
|
|
1763
|
+
isStreaming: false,
|
|
1764
|
+
usage: detail?.usage,
|
|
1765
|
+
elapsed: detail?.elapsed,
|
|
1766
|
+
cost: detail?.cost,
|
|
1767
|
+
model: detail?.model
|
|
1768
|
+
},
|
|
1769
|
+
i
|
|
1770
|
+
);
|
|
1771
|
+
} }),
|
|
1772
|
+
isStreaming && /* @__PURE__ */ jsx5(
|
|
1773
|
+
AssistantMessage,
|
|
1774
|
+
{
|
|
1775
|
+
content: currentContent,
|
|
1776
|
+
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
|
|
1777
|
+
isStreaming: true
|
|
1778
|
+
}
|
|
1779
|
+
),
|
|
1780
|
+
isStreaming && !currentContent && currentToolCalls.length === 0 && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, marginLeft: 4, children: /* @__PURE__ */ jsx5(Spinner, { type: "dots", label: "\u601D\u8003\u4E2D..." }) }),
|
|
1781
|
+
!isStreaming && streamError && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
|
|
1782
|
+
"\u26A0 ",
|
|
1783
|
+
streamError
|
|
1784
|
+
] }) })
|
|
1785
|
+
] }),
|
|
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(
|
|
645
1790
|
TextInput,
|
|
646
1791
|
{
|
|
647
1792
|
value: input,
|
|
648
1793
|
onChange: setInput,
|
|
649
1794
|
onSubmit: handleSubmit,
|
|
650
|
-
placeholder: "
|
|
1795
|
+
placeholder: ""
|
|
651
1796
|
}
|
|
652
1797
|
) })
|
|
653
1798
|
] }),
|
|
654
|
-
/* @__PURE__ */
|
|
655
|
-
doubleCtrlC && /* @__PURE__ */
|
|
1799
|
+
/* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
|
|
1800
|
+
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
|
+
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" }) })
|
|
656
1802
|
] });
|
|
657
1803
|
}
|
|
658
1804
|
|
|
659
1805
|
// src/ui/GamePicker.tsx
|
|
660
|
-
import { Box as
|
|
1806
|
+
import { Box as Box6, Text as Text7, useInput as useInput2 } from "ink";
|
|
661
1807
|
import { useState as useState4, useCallback as useCallback3 } from "react";
|
|
662
|
-
import { jsx as
|
|
1808
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
663
1809
|
function GamePicker({ games, onSelect, onExit, onBackToChat }) {
|
|
664
1810
|
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
665
1811
|
const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
|
|
@@ -687,18 +1833,18 @@ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
|
|
|
687
1833
|
[games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
|
|
688
1834
|
)
|
|
689
1835
|
);
|
|
690
|
-
return /* @__PURE__ */
|
|
691
|
-
/* @__PURE__ */
|
|
692
|
-
/* @__PURE__ */
|
|
1836
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
1837
|
+
/* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text7, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
|
|
1838
|
+
/* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: games.map((game, index) => {
|
|
693
1839
|
const isSelected = index === selectedIndex;
|
|
694
|
-
return /* @__PURE__ */
|
|
695
|
-
/* @__PURE__ */
|
|
696
|
-
/* @__PURE__ */
|
|
697
|
-
/* @__PURE__ */
|
|
1840
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", children: [
|
|
1841
|
+
/* @__PURE__ */ jsx6(Box6, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx6(Text7, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx6(Text7, { children: " " }) }),
|
|
1842
|
+
/* @__PURE__ */ jsx6(Box6, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text7, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
|
|
1843
|
+
/* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text7, { color: "#888888", children: game.description }) })
|
|
698
1844
|
] }, game.id);
|
|
699
1845
|
}) }),
|
|
700
|
-
/* @__PURE__ */
|
|
701
|
-
doubleCtrlC && /* @__PURE__ */
|
|
1846
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
|
|
1847
|
+
doubleCtrlC && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
|
|
702
1848
|
] });
|
|
703
1849
|
}
|
|
704
1850
|
|
|
@@ -715,9 +1861,9 @@ function listGames() {
|
|
|
715
1861
|
}
|
|
716
1862
|
|
|
717
1863
|
// src/game/brick-breaker/index.tsx
|
|
718
|
-
import { Box as
|
|
719
|
-
import { useState as useState5, useEffect as useEffect4, useRef as
|
|
720
|
-
import { jsx as
|
|
1864
|
+
import { Box as Box7, Text as Text8, useInput as useInput3, render as render2 } from "ink";
|
|
1865
|
+
import { useState as useState5, useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4 } from "react";
|
|
1866
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
721
1867
|
var GAME_WIDTH = 40;
|
|
722
1868
|
var GAME_HEIGHT = 18;
|
|
723
1869
|
var PADDLE_WIDTH = 9;
|
|
@@ -850,9 +1996,9 @@ function buildBoard(state) {
|
|
|
850
1996
|
function BrickBreakerGame({ onExit: _onExit }) {
|
|
851
1997
|
const [initialLevel, setInitialLevel] = useState5(1);
|
|
852
1998
|
const [selectingLevel, setSelectingLevel] = useState5(true);
|
|
853
|
-
const stateRef =
|
|
1999
|
+
const stateRef = useRef3(createInitialState(initialLevel));
|
|
854
2000
|
const [tick, setTick] = useState5(0);
|
|
855
|
-
const onExitRef =
|
|
2001
|
+
const onExitRef = useRef3(_onExit);
|
|
856
2002
|
onExitRef.current = _onExit;
|
|
857
2003
|
useEffect4(() => {
|
|
858
2004
|
if (selectingLevel) return;
|
|
@@ -907,49 +2053,49 @@ function BrickBreakerGame({ onExit: _onExit }) {
|
|
|
907
2053
|
const board = buildBoard(s);
|
|
908
2054
|
const def = getLevel(s.level);
|
|
909
2055
|
void tick;
|
|
910
|
-
return /* @__PURE__ */
|
|
911
|
-
/* @__PURE__ */
|
|
912
|
-
/* @__PURE__ */
|
|
2056
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2057
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", children: [
|
|
2058
|
+
/* @__PURE__ */ jsx7(Box7, { width: 20, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
913
2059
|
"\u5173\u5361 ",
|
|
914
2060
|
s.level,
|
|
915
2061
|
": ",
|
|
916
|
-
/* @__PURE__ */
|
|
2062
|
+
/* @__PURE__ */ jsx7(Text8, { color: "cyan", children: def.desc })
|
|
917
2063
|
] }) }),
|
|
918
|
-
/* @__PURE__ */
|
|
2064
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
919
2065
|
"\u5206\u6570: ",
|
|
920
|
-
/* @__PURE__ */
|
|
2066
|
+
/* @__PURE__ */ jsx7(Text8, { color: "yellow", children: String(s.score).padStart(3, "0") })
|
|
921
2067
|
] }) }),
|
|
922
|
-
/* @__PURE__ */
|
|
2068
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
923
2069
|
"\u751F\u547D: ",
|
|
924
|
-
/* @__PURE__ */
|
|
2070
|
+
/* @__PURE__ */ jsx7(Text8, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
|
|
925
2071
|
] }) }),
|
|
926
|
-
/* @__PURE__ */
|
|
2072
|
+
/* @__PURE__ */ jsx7(Box7, { width: 10, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
927
2073
|
"\u7816\u5757: ",
|
|
928
|
-
/* @__PURE__ */
|
|
2074
|
+
/* @__PURE__ */ jsx7(Text8, { color: "cyan", children: aliveCount })
|
|
929
2075
|
] }) }),
|
|
930
|
-
/* @__PURE__ */
|
|
2076
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: s.paused ? "gray" : "green", children: [
|
|
931
2077
|
"[",
|
|
932
2078
|
s.paused ? "\u6682\u505C" : "\u8FD0\u884C\u4E2D",
|
|
933
2079
|
"]"
|
|
934
2080
|
] }) })
|
|
935
2081
|
] }),
|
|
936
|
-
/* @__PURE__ */
|
|
937
|
-
/* @__PURE__ */
|
|
2082
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2083
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
938
2084
|
"\u250C",
|
|
939
2085
|
"\u2500".repeat(GAME_WIDTH),
|
|
940
2086
|
"\u2510"
|
|
941
2087
|
] }),
|
|
942
|
-
/* @__PURE__ */
|
|
943
|
-
/* @__PURE__ */
|
|
2088
|
+
/* @__PURE__ */ jsx7(Text8, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
|
|
2089
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
944
2090
|
"\u2514",
|
|
945
2091
|
"\u2500".repeat(GAME_WIDTH),
|
|
946
2092
|
"\u2518"
|
|
947
2093
|
] })
|
|
948
2094
|
] }),
|
|
949
|
-
selectingLevel && /* @__PURE__ */
|
|
950
|
-
/* @__PURE__ */
|
|
951
|
-
/* @__PURE__ */
|
|
952
|
-
/* @__PURE__ */
|
|
2095
|
+
selectingLevel && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
2096
|
+
/* @__PURE__ */ jsx7(Text8, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
|
|
2097
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx7(Box7, { width: 22, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
2098
|
+
/* @__PURE__ */ jsx7(Text8, { color: initialLevel === i + 1 ? "green" : "white", children: i + 1 === 10 ? "0" : String(i + 1) }),
|
|
953
2099
|
". ",
|
|
954
2100
|
lv.desc,
|
|
955
2101
|
" (",
|
|
@@ -958,16 +2104,16 @@ function BrickBreakerGame({ onExit: _onExit }) {
|
|
|
958
2104
|
lv.cols,
|
|
959
2105
|
")"
|
|
960
2106
|
] }) }, i)) }),
|
|
961
|
-
/* @__PURE__ */
|
|
2107
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
|
|
962
2108
|
] }),
|
|
963
|
-
!selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */
|
|
964
|
-
/* @__PURE__ */
|
|
965
|
-
/* @__PURE__ */
|
|
2109
|
+
!selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
|
|
2110
|
+
/* @__PURE__ */ jsx7(Text8, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
|
|
2111
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
966
2112
|
" \u5206\u6570: ",
|
|
967
|
-
/* @__PURE__ */
|
|
2113
|
+
/* @__PURE__ */ jsx7(Text8, { color: "yellow", children: s.score })
|
|
968
2114
|
] })
|
|
969
2115
|
] }),
|
|
970
|
-
!selectingLevel && /* @__PURE__ */
|
|
2116
|
+
!selectingLevel && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: s.gameOver || s.win ? /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 r \u91CD\u5F00 l \u9009\u5173 q \u9000\u51FA" }) : /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 p \u6682\u505C q \u9000\u51FA" }) })
|
|
971
2117
|
] });
|
|
972
2118
|
}
|
|
973
2119
|
var brick_breaker_default = {
|
|
@@ -977,7 +2123,7 @@ var brick_breaker_default = {
|
|
|
977
2123
|
play: async () => {
|
|
978
2124
|
await new Promise((resolve) => {
|
|
979
2125
|
const { unmount } = render2(
|
|
980
|
-
/* @__PURE__ */
|
|
2126
|
+
/* @__PURE__ */ jsx7(BrickBreakerGame, { onExit: () => {
|
|
981
2127
|
unmount();
|
|
982
2128
|
resolve();
|
|
983
2129
|
} })
|
|
@@ -987,9 +2133,9 @@ var brick_breaker_default = {
|
|
|
987
2133
|
};
|
|
988
2134
|
|
|
989
2135
|
// src/game/coder-check/index.tsx
|
|
990
|
-
import { Box as
|
|
991
|
-
import { useState as useState6, useEffect as useEffect5, useRef as
|
|
992
|
-
import { jsx as
|
|
2136
|
+
import { Box as Box8, Text as Text9, useInput as useInput4, render as render3 } from "ink";
|
|
2137
|
+
import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback5 } from "react";
|
|
2138
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
993
2139
|
var GAME_W = 66;
|
|
994
2140
|
var GAME_H = 20;
|
|
995
2141
|
var SCORE_H = 6;
|
|
@@ -1380,10 +2526,10 @@ function buildGameView(s, scoreLines, scoreColor, message) {
|
|
|
1380
2526
|
return rows;
|
|
1381
2527
|
}
|
|
1382
2528
|
function CoderCheck({ onExit: _onExit }) {
|
|
1383
|
-
const stateRef =
|
|
2529
|
+
const stateRef = useRef4(createInitialState2());
|
|
1384
2530
|
const [tick, setTick] = useState6(0);
|
|
1385
2531
|
const [colorOffset, setColorOffset] = useState6(0);
|
|
1386
|
-
const onExitRef =
|
|
2532
|
+
const onExitRef = useRef4(_onExit);
|
|
1387
2533
|
onExitRef.current = _onExit;
|
|
1388
2534
|
useEffect5(() => {
|
|
1389
2535
|
const timer = setInterval(() => {
|
|
@@ -1460,44 +2606,44 @@ function CoderCheck({ onExit: _onExit }) {
|
|
|
1460
2606
|
const scoreLines = buildScoreLines(scoreStr);
|
|
1461
2607
|
const view = buildGameView(s, scoreLines, scoreColor, s.message);
|
|
1462
2608
|
void tick;
|
|
1463
|
-
return /* @__PURE__ */
|
|
1464
|
-
/* @__PURE__ */
|
|
2609
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
2610
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "row", children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1465
2611
|
"\u751F\u547D ",
|
|
1466
|
-
/* @__PURE__ */
|
|
2612
|
+
/* @__PURE__ */ jsx8(Text9, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
|
|
1467
2613
|
" ",
|
|
1468
2614
|
"\u901F\u5EA6 ",
|
|
1469
|
-
/* @__PURE__ */
|
|
2615
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
|
|
1470
2616
|
"Lv.",
|
|
1471
2617
|
Math.floor(s.speed * 10)
|
|
1472
2618
|
] })
|
|
1473
2619
|
] }) }),
|
|
1474
|
-
!s.gameOver && s.target && /* @__PURE__ */
|
|
2620
|
+
!s.gameOver && s.target && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1475
2621
|
"\u6253\u5B57: ",
|
|
1476
|
-
/* @__PURE__ */
|
|
1477
|
-
/* @__PURE__ */
|
|
2622
|
+
/* @__PURE__ */ jsx8(Text9, { color: "green", children: s.typed }),
|
|
2623
|
+
/* @__PURE__ */ jsx8(Text9, { color: "white", children: s.target.slice(s.typed.length) })
|
|
1478
2624
|
] }) }),
|
|
1479
|
-
/* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
2625
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
2626
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1481
2627
|
"\u250C",
|
|
1482
2628
|
"\u2500".repeat(GAME_W),
|
|
1483
2629
|
"\u2510"
|
|
1484
2630
|
] }),
|
|
1485
|
-
view.map((row, i) => /* @__PURE__ */
|
|
1486
|
-
/* @__PURE__ */
|
|
2631
|
+
view.map((row, i) => /* @__PURE__ */ jsx8(Text9, { children: `\u2502${row}\u2502` }, i)),
|
|
2632
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1487
2633
|
"\u2514",
|
|
1488
2634
|
"\u2500".repeat(GAME_W),
|
|
1489
2635
|
"\u2518"
|
|
1490
2636
|
] })
|
|
1491
2637
|
] }),
|
|
1492
|
-
s.gameOver && /* @__PURE__ */
|
|
1493
|
-
/* @__PURE__ */
|
|
1494
|
-
/* @__PURE__ */
|
|
2638
|
+
s.gameOver && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
2639
|
+
/* @__PURE__ */ jsx8(Text9, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
|
|
2640
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1495
2641
|
" \u5F97\u5206: ",
|
|
1496
|
-
/* @__PURE__ */
|
|
2642
|
+
/* @__PURE__ */ jsx8(Text9, { color: "yellow", children: s.score }),
|
|
1497
2643
|
" r \u91CD\u5F00 q \u9000\u51FA"
|
|
1498
2644
|
] })
|
|
1499
2645
|
] }),
|
|
1500
|
-
/* @__PURE__ */
|
|
2646
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: "\u6253\u5B57\u6D88\u9664\u5355\u8BCD \u7A7A\u683C/Ctrl+P\u6682\u505C Ctrl+Q\u9000\u51FA" }) })
|
|
1501
2647
|
] });
|
|
1502
2648
|
}
|
|
1503
2649
|
var coder_check_default = {
|
|
@@ -1507,7 +2653,7 @@ var coder_check_default = {
|
|
|
1507
2653
|
play: async () => {
|
|
1508
2654
|
await new Promise((resolve) => {
|
|
1509
2655
|
const { unmount } = render3(
|
|
1510
|
-
/* @__PURE__ */
|
|
2656
|
+
/* @__PURE__ */ jsx8(CoderCheck, { onExit: () => {
|
|
1511
2657
|
unmount();
|
|
1512
2658
|
resolve();
|
|
1513
2659
|
} })
|
|
@@ -1528,10 +2674,10 @@ import { render as render4 } from "ink";
|
|
|
1528
2674
|
import chalk3 from "chalk";
|
|
1529
2675
|
|
|
1530
2676
|
// src/stock/StockList.tsx
|
|
1531
|
-
import { Box as
|
|
2677
|
+
import { Box as Box9, Text as Text10, useInput as useInput5 } from "ink";
|
|
1532
2678
|
import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6 } from "react";
|
|
1533
2679
|
import asciichart from "asciichart";
|
|
1534
|
-
import { jsx as
|
|
2680
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1535
2681
|
var MINUTE_API = "https://web.ifzq.gtimg.cn/appstock/app/minute/query?code={code}&r=0.1";
|
|
1536
2682
|
function normalizeApiCode(code) {
|
|
1537
2683
|
if (code.startsWith("sh") || code.startsWith("sz")) return code;
|
|
@@ -1728,57 +2874,57 @@ function StockList({ codes, onExit, onBackToChat }) {
|
|
|
1728
2874
|
);
|
|
1729
2875
|
if (detailView) {
|
|
1730
2876
|
if (detailLoading) {
|
|
1731
|
-
return /* @__PURE__ */
|
|
2877
|
+
return /* @__PURE__ */ jsx9(Box9, { paddingLeft: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
|
|
1732
2878
|
}
|
|
1733
2879
|
return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown, currentTime);
|
|
1734
2880
|
}
|
|
1735
|
-
return /* @__PURE__ */
|
|
1736
|
-
/* @__PURE__ */
|
|
1737
|
-
/* @__PURE__ */
|
|
1738
|
-
/* @__PURE__ */
|
|
2881
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2882
|
+
/* @__PURE__ */ jsxs9(Box9, { marginBottom: 1, justifyContent: "space-between", children: [
|
|
2883
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
|
|
2884
|
+
/* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
|
|
1739
2885
|
" \u{1F550} ",
|
|
1740
2886
|
currentTime
|
|
1741
2887
|
] }),
|
|
1742
|
-
/* @__PURE__ */
|
|
2888
|
+
/* @__PURE__ */ jsx9(Text10, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
|
|
1743
2889
|
] }),
|
|
1744
|
-
/* @__PURE__ */
|
|
1745
|
-
/* @__PURE__ */
|
|
1746
|
-
/* @__PURE__ */
|
|
1747
|
-
/* @__PURE__ */
|
|
1748
|
-
/* @__PURE__ */
|
|
1749
|
-
/* @__PURE__ */
|
|
1750
|
-
/* @__PURE__ */
|
|
1751
|
-
/* @__PURE__ */
|
|
1752
|
-
/* @__PURE__ */
|
|
1753
|
-
/* @__PURE__ */
|
|
2890
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2891
|
+
/* @__PURE__ */ jsx9(Box9, { width: 3 }),
|
|
2892
|
+
/* @__PURE__ */ jsx9(Box9, { width: 9, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u4EE3\u7801" }) }),
|
|
2893
|
+
/* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u540D\u79F0" }) }),
|
|
2894
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
|
|
2895
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
|
|
2896
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
|
|
2897
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u9AD8" }) }),
|
|
2898
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u4F4E" }) }),
|
|
2899
|
+
/* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
|
|
1754
2900
|
] }),
|
|
1755
|
-
/* @__PURE__ */
|
|
1756
|
-
/* @__PURE__ */
|
|
2901
|
+
/* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
|
|
2902
|
+
/* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: stocks.map((stock, index) => {
|
|
1757
2903
|
const isSelected = index === selectedIndex;
|
|
1758
2904
|
const isUp = stock.changePercent >= 0;
|
|
1759
2905
|
const color = isUp ? "#ff1493" : "#00ff41";
|
|
1760
|
-
return /* @__PURE__ */
|
|
1761
|
-
/* @__PURE__ */
|
|
1762
|
-
/* @__PURE__ */
|
|
1763
|
-
/* @__PURE__ */
|
|
1764
|
-
/* @__PURE__ */
|
|
1765
|
-
/* @__PURE__ */
|
|
2906
|
+
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2907
|
+
/* @__PURE__ */ jsx9(Box9, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx9(Text10, { children: " " }) }),
|
|
2908
|
+
/* @__PURE__ */ jsx9(Box9, { width: 9, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
|
|
2909
|
+
/* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
|
|
2910
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color, children: formatPrice(stock.price) }) }),
|
|
2911
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
|
|
1766
2912
|
isUp ? "+" : "",
|
|
1767
2913
|
stock.changePercent.toFixed(2),
|
|
1768
2914
|
"%"
|
|
1769
2915
|
] }) }),
|
|
1770
|
-
/* @__PURE__ */
|
|
2916
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
|
|
1771
2917
|
isUp ? "+" : "",
|
|
1772
2918
|
stock.changeAmount.toFixed(3)
|
|
1773
2919
|
] }) }),
|
|
1774
|
-
/* @__PURE__ */
|
|
1775
|
-
/* @__PURE__ */
|
|
1776
|
-
/* @__PURE__ */
|
|
2920
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { color: "#cccccc", children: formatPrice(stock.high) }) }),
|
|
2921
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: formatPrice(stock.low) }) }),
|
|
2922
|
+
/* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: formatAmount(stock.amount) }) })
|
|
1777
2923
|
] }, stock.code);
|
|
1778
2924
|
}) }),
|
|
1779
|
-
/* @__PURE__ */
|
|
1780
|
-
/* @__PURE__ */
|
|
1781
|
-
doubleCtrlC && /* @__PURE__ */
|
|
2925
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: ` \u2191/\u2193 \u9009\u62E9 Enter \u8BE6\u60C5 r \u624B\u52A8\u5237\u65B0 q \u8FD4\u56DE` }) }),
|
|
2926
|
+
/* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ~/.dskcode/settings.json` }) }),
|
|
2927
|
+
doubleCtrlC && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
|
|
1782
2928
|
] });
|
|
1783
2929
|
}
|
|
1784
2930
|
function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
|
|
@@ -1796,33 +2942,33 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
|
|
|
1796
2942
|
raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
|
|
1797
2943
|
chartLines = raw.split("\n");
|
|
1798
2944
|
}
|
|
1799
|
-
return /* @__PURE__ */
|
|
1800
|
-
/* @__PURE__ */
|
|
1801
|
-
/* @__PURE__ */
|
|
1802
|
-
/* @__PURE__ */
|
|
2945
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingLeft: 1, children: [
|
|
2946
|
+
/* @__PURE__ */ jsxs9(Box9, { marginBottom: 1, justifyContent: "space-between", children: [
|
|
2947
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2948
|
+
/* @__PURE__ */ jsxs9(Text10, { bold: true, color: "#00ffff", children: [
|
|
1803
2949
|
" \u{1F4CA} ",
|
|
1804
2950
|
stock.name,
|
|
1805
2951
|
" "
|
|
1806
2952
|
] }),
|
|
1807
|
-
/* @__PURE__ */
|
|
1808
|
-
currentTime && /* @__PURE__ */
|
|
2953
|
+
/* @__PURE__ */ jsx9(Text10, { dimColor: true, children: stock.code }),
|
|
2954
|
+
currentTime && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
|
|
1809
2955
|
" \u{1F550} ",
|
|
1810
2956
|
currentTime
|
|
1811
2957
|
] })
|
|
1812
2958
|
] }),
|
|
1813
|
-
/* @__PURE__ */
|
|
2959
|
+
/* @__PURE__ */ jsx9(Text10, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
|
|
1814
2960
|
] }),
|
|
1815
|
-
/* @__PURE__ */
|
|
1816
|
-
/* @__PURE__ */
|
|
1817
|
-
/* @__PURE__ */
|
|
2961
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2962
|
+
/* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
|
|
2963
|
+
/* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { bold: true, color: colorCode, children: [
|
|
1818
2964
|
arrow,
|
|
1819
2965
|
" ",
|
|
1820
2966
|
formatPrice(stock.price)
|
|
1821
2967
|
] }) })
|
|
1822
2968
|
] }),
|
|
1823
|
-
/* @__PURE__ */
|
|
1824
|
-
/* @__PURE__ */
|
|
1825
|
-
/* @__PURE__ */
|
|
2969
|
+
/* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2970
|
+
/* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
|
|
2971
|
+
/* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: colorCode, children: [
|
|
1826
2972
|
isUp ? "+" : "",
|
|
1827
2973
|
stock.changePercent.toFixed(2),
|
|
1828
2974
|
"%",
|
|
@@ -1831,13 +2977,13 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
|
|
|
1831
2977
|
stock.changeAmount.toFixed(3)
|
|
1832
2978
|
] }) })
|
|
1833
2979
|
] }),
|
|
1834
|
-
chartLines.length > 0 && /* @__PURE__ */
|
|
1835
|
-
/* @__PURE__ */
|
|
2980
|
+
chartLines.length > 0 && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: chartLines.map((line, i) => /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: colorCode, children: line || " " }) }, i)) }),
|
|
2981
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
|
|
1836
2982
|
] });
|
|
1837
2983
|
}
|
|
1838
2984
|
|
|
1839
2985
|
// src/cli/index.tsx
|
|
1840
|
-
import { jsx as
|
|
2986
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1841
2987
|
var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
|
|
1842
2988
|
function createCli() {
|
|
1843
2989
|
const program2 = new Command();
|
|
@@ -1863,14 +3009,19 @@ function createCli() {
|
|
|
1863
3009
|
const result = await loadAndValidate();
|
|
1864
3010
|
ctx = { ...ctx, config: result.config };
|
|
1865
3011
|
}
|
|
1866
|
-
|
|
3012
|
+
const costTracker = new CostTracker({
|
|
3013
|
+
budgetLimit: ctx?.config.budgetLimit ?? 0,
|
|
3014
|
+
tokenBudgetLimit: ctx?.config.tokenBudgetLimit ?? 0
|
|
3015
|
+
});
|
|
3016
|
+
startChat(ctx, costTracker);
|
|
1867
3017
|
});
|
|
1868
|
-
function startChat(ctx) {
|
|
3018
|
+
function startChat(ctx, costTracker) {
|
|
1869
3019
|
const defaultProvider = ctx?.config.providers.find(
|
|
1870
3020
|
(p) => p.name === (ctx?.config.defaultProvider ?? "deepseek")
|
|
1871
3021
|
);
|
|
3022
|
+
const model = defaultProvider?.model ?? "deepseek-v4-flash";
|
|
1872
3023
|
const chatApp = renderApp(
|
|
1873
|
-
/* @__PURE__ */
|
|
3024
|
+
/* @__PURE__ */ jsx10(
|
|
1874
3025
|
ChatSession,
|
|
1875
3026
|
{
|
|
1876
3027
|
providerCount: ctx?.config.providers.length ?? 1,
|
|
@@ -1878,24 +3029,26 @@ function createCli() {
|
|
|
1878
3029
|
verbose: ctx?.verbose ?? false,
|
|
1879
3030
|
apiKey: defaultProvider?.apiKey,
|
|
1880
3031
|
baseUrl: defaultProvider?.baseUrl ?? "https://api.deepseek.com",
|
|
3032
|
+
costTracker,
|
|
3033
|
+
model,
|
|
1881
3034
|
onLaunchGame: () => {
|
|
1882
3035
|
chatApp.unmount();
|
|
1883
3036
|
setImmediate(() => {
|
|
1884
3037
|
initGames();
|
|
1885
3038
|
const games = listGames();
|
|
1886
3039
|
const { unmount } = render4(
|
|
1887
|
-
/* @__PURE__ */
|
|
3040
|
+
/* @__PURE__ */ jsx10(
|
|
1888
3041
|
GamePicker,
|
|
1889
3042
|
{
|
|
1890
3043
|
games,
|
|
1891
3044
|
onSelect: async (game) => {
|
|
1892
3045
|
unmount();
|
|
1893
3046
|
await game.play();
|
|
1894
|
-
startChat(ctx);
|
|
3047
|
+
startChat(ctx, costTracker);
|
|
1895
3048
|
},
|
|
1896
3049
|
onBackToChat: () => {
|
|
1897
3050
|
unmount();
|
|
1898
|
-
setImmediate(() => startChat(ctx));
|
|
3051
|
+
setImmediate(() => startChat(ctx, costTracker));
|
|
1899
3052
|
}
|
|
1900
3053
|
}
|
|
1901
3054
|
),
|
|
@@ -1908,13 +3061,13 @@ function createCli() {
|
|
|
1908
3061
|
setImmediate(() => {
|
|
1909
3062
|
const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
|
|
1910
3063
|
const stockApp = renderApp(
|
|
1911
|
-
/* @__PURE__ */
|
|
3064
|
+
/* @__PURE__ */ jsx10(
|
|
1912
3065
|
StockList,
|
|
1913
3066
|
{
|
|
1914
3067
|
codes: defaultStockCodes,
|
|
1915
3068
|
onBackToChat: () => {
|
|
1916
3069
|
stockApp.unmount();
|
|
1917
|
-
setImmediate(() => startChat(ctx));
|
|
3070
|
+
setImmediate(() => startChat(ctx, costTracker));
|
|
1918
3071
|
},
|
|
1919
3072
|
onExit: () => process.exit(0)
|
|
1920
3073
|
}
|
|
@@ -1972,10 +3125,10 @@ compdef _dskcode_completion dskcode`);
|
|
|
1972
3125
|
program2.command("stock").description("\u67E5\u770B\u81EA\u9009\u80A1\u5B9E\u65F6\u884C\u60C5").argument("[codes...]", "\u80A1\u7968\u4EE3\u7801\uFF08\u7A7A\u683C\u5206\u9694\uFF09\uFF0C\u5982 513090 600519").action(async function(codes) {
|
|
1973
3126
|
const ctx = this.dskcodeCtx;
|
|
1974
3127
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
1975
|
-
const globalConfigPath =
|
|
3128
|
+
const globalConfigPath = join3(home, ".dskcode", "settings.json");
|
|
1976
3129
|
let globalConfigHasStock = false;
|
|
1977
3130
|
try {
|
|
1978
|
-
const raw = await
|
|
3131
|
+
const raw = await readFile3(globalConfigPath, "utf-8");
|
|
1979
3132
|
const parsed = JSON.parse(raw);
|
|
1980
3133
|
const stock = parsed.stock;
|
|
1981
3134
|
globalConfigHasStock = Array.isArray(stock?.symbols) && stock.symbols.length > 0;
|
|
@@ -1995,7 +3148,7 @@ compdef _dskcode_completion dskcode`);
|
|
|
1995
3148
|
const freshResult = await loadAndValidate();
|
|
1996
3149
|
const codeList = codes && codes.length > 0 ? codes : freshResult.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
|
|
1997
3150
|
const app = renderApp(
|
|
1998
|
-
/* @__PURE__ */
|
|
3151
|
+
/* @__PURE__ */ jsx10(
|
|
1999
3152
|
StockList,
|
|
2000
3153
|
{
|
|
2001
3154
|
codes: codeList,
|
|
@@ -2024,7 +3177,7 @@ compdef _dskcode_completion dskcode`);
|
|
|
2024
3177
|
}
|
|
2025
3178
|
const selectedGame = await new Promise((resolve) => {
|
|
2026
3179
|
const { unmount } = render4(
|
|
2027
|
-
/* @__PURE__ */
|
|
3180
|
+
/* @__PURE__ */ jsx10(
|
|
2028
3181
|
GamePicker,
|
|
2029
3182
|
{
|
|
2030
3183
|
games,
|