dskcode 0.1.9 → 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 +1362 -167
- 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 +2 -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,6 +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
|
}
|
|
195
|
+
const SUPPORTED_MODELS2 = ["deepseek-v4-flash", "deepseek-v4-pro"];
|
|
169
196
|
for (let i = 0; i < config.providers.length; i++) {
|
|
170
197
|
const p = config.providers[i];
|
|
171
198
|
if (!p.name) {
|
|
@@ -179,6 +206,11 @@ function validateConfig(config) {
|
|
|
179
206
|
field: `providers[${i}].model`,
|
|
180
207
|
message: `Provider "${p.name || i}" \u7F3A\u5C11 model \u5B57\u6BB5\u3002`
|
|
181
208
|
});
|
|
209
|
+
} else if (!SUPPORTED_MODELS2.includes(p.model)) {
|
|
210
|
+
errors.push({
|
|
211
|
+
field: `providers[${i}].model`,
|
|
212
|
+
message: `Provider "${p.name || i}" \u7684 model "${p.model}" \u4E0D\u53D7\u652F\u6301\u3002dskcode \u4EC5\u652F\u6301: ${SUPPORTED_MODELS2.join(", ")}`
|
|
213
|
+
});
|
|
182
214
|
}
|
|
183
215
|
}
|
|
184
216
|
if (config.defaultProvider) {
|
|
@@ -210,6 +242,18 @@ function validateConfig(config) {
|
|
|
210
242
|
message: "maxToolRounds \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E 1\u3002"
|
|
211
243
|
});
|
|
212
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
|
+
}
|
|
213
257
|
return errors;
|
|
214
258
|
}
|
|
215
259
|
async function loadConfig(configPath) {
|
|
@@ -415,8 +459,8 @@ async function promptForApiKey() {
|
|
|
415
459
|
}
|
|
416
460
|
|
|
417
461
|
// src/cli/index.tsx
|
|
418
|
-
import { readFile as
|
|
419
|
-
import { join as
|
|
462
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
463
|
+
import { join as join3 } from "path";
|
|
420
464
|
|
|
421
465
|
// src/ui/RenderScope.tsx
|
|
422
466
|
import { render } from "ink";
|
|
@@ -429,6 +473,15 @@ function renderApp(node) {
|
|
|
429
473
|
import { Text } from "ink";
|
|
430
474
|
import InkSpinner from "ink-spinner";
|
|
431
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
|
+
}
|
|
432
485
|
|
|
433
486
|
// src/ui/StatusMessage.tsx
|
|
434
487
|
import { Box, Text as Text2 } from "ink";
|
|
@@ -449,9 +502,9 @@ var LOGO_LINES = [
|
|
|
449
502
|
];
|
|
450
503
|
|
|
451
504
|
// src/ui/ChatSession.tsx
|
|
452
|
-
import { Box as
|
|
505
|
+
import { Box as Box5, Text as Text6, useInput, Static } from "ink";
|
|
453
506
|
import TextInput from "ink-text-input";
|
|
454
|
-
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";
|
|
455
508
|
|
|
456
509
|
// src/ui/useDoubleCtrlC.ts
|
|
457
510
|
import { useState as useState2, useCallback, useEffect as useEffect2, useRef } from "react";
|
|
@@ -481,8 +534,824 @@ function useDoubleCtrlC(onExit) {
|
|
|
481
534
|
return { doubleCtrlC, handleCtrlC };
|
|
482
535
|
}
|
|
483
536
|
|
|
484
|
-
// 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";
|
|
485
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";
|
|
486
1355
|
var commandRegistry = /* @__PURE__ */ new Map();
|
|
487
1356
|
function registerCommand(name, cmd) {
|
|
488
1357
|
commandRegistry.set(name, cmd);
|
|
@@ -504,22 +1373,89 @@ registerCommand("/help", {
|
|
|
504
1373
|
}
|
|
505
1374
|
});
|
|
506
1375
|
registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
|
|
507
|
-
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" }) });
|
|
508
1377
|
registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
|
|
509
1378
|
registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
|
|
510
|
-
|
|
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);
|
|
511
1411
|
const [offset, setOffset] = useState3(0);
|
|
512
|
-
const [
|
|
1412
|
+
const [displayMessages, setDisplayMessages] = useState3([]);
|
|
513
1413
|
const [input, setInput] = useState3("");
|
|
514
|
-
const
|
|
1414
|
+
const [balance, setBalance] = useState3(null);
|
|
1415
|
+
const [balanceLoading, setBalanceLoading] = useState3(false);
|
|
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
|
+
});
|
|
515
1443
|
useInput(
|
|
516
1444
|
useCallback2(
|
|
517
|
-
(
|
|
518
|
-
if (
|
|
519
|
-
|
|
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);
|
|
520
1456
|
}
|
|
521
1457
|
},
|
|
522
|
-
[handleCtrlC]
|
|
1458
|
+
[isStreaming, handleCtrlC, input]
|
|
523
1459
|
)
|
|
524
1460
|
);
|
|
525
1461
|
useEffect3(() => {
|
|
@@ -528,7 +1464,93 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
|
|
|
528
1464
|
}, 500);
|
|
529
1465
|
return () => clearInterval(timer);
|
|
530
1466
|
}, []);
|
|
531
|
-
|
|
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]);
|
|
1484
|
+
useEffect3(() => {
|
|
1485
|
+
if (!apiKey || !baseUrl) return;
|
|
1486
|
+
let cancelled = false;
|
|
1487
|
+
setBalanceLoading(true);
|
|
1488
|
+
import("./deepseek-OIJOG3N5.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
|
|
1489
|
+
const provider = new DeepSeekProvider2({
|
|
1490
|
+
apiKey,
|
|
1491
|
+
baseUrl,
|
|
1492
|
+
model: "deepseek-v4-flash"
|
|
1493
|
+
});
|
|
1494
|
+
return provider.getBalance();
|
|
1495
|
+
}).then((result) => {
|
|
1496
|
+
if (cancelled) return;
|
|
1497
|
+
const cny = result.balances.find((b) => b.currency === "CNY");
|
|
1498
|
+
if (cny) {
|
|
1499
|
+
setBalance(cny.totalBalance);
|
|
1500
|
+
}
|
|
1501
|
+
}).catch(() => {
|
|
1502
|
+
}).finally(() => {
|
|
1503
|
+
if (!cancelled) setBalanceLoading(false);
|
|
1504
|
+
});
|
|
1505
|
+
return () => {
|
|
1506
|
+
cancelled = true;
|
|
1507
|
+
};
|
|
1508
|
+
}, [apiKey, baseUrl]);
|
|
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) => {
|
|
532
1554
|
const trimmed = value.trim();
|
|
533
1555
|
if (!trimmed) return;
|
|
534
1556
|
if (trimmed.startsWith("/")) {
|
|
@@ -540,8 +1562,9 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
|
|
|
540
1562
|
process.exit(0);
|
|
541
1563
|
return;
|
|
542
1564
|
case "clear":
|
|
543
|
-
|
|
1565
|
+
setDisplayMessages([]);
|
|
544
1566
|
setInput("");
|
|
1567
|
+
sessionRef.current?.reset();
|
|
545
1568
|
return;
|
|
546
1569
|
case "navigate":
|
|
547
1570
|
setInput("");
|
|
@@ -552,7 +1575,7 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
|
|
|
552
1575
|
}
|
|
553
1576
|
return;
|
|
554
1577
|
case "text":
|
|
555
|
-
|
|
1578
|
+
setDisplayMessages((prev) => [
|
|
556
1579
|
...prev,
|
|
557
1580
|
{ role: "user", content: trimmed },
|
|
558
1581
|
{ role: "assistant", content: result.content }
|
|
@@ -561,7 +1584,7 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
|
|
|
561
1584
|
return;
|
|
562
1585
|
}
|
|
563
1586
|
}
|
|
564
|
-
|
|
1587
|
+
setDisplayMessages((prev) => [
|
|
565
1588
|
...prev,
|
|
566
1589
|
{ role: "user", content: trimmed },
|
|
567
1590
|
{ role: "assistant", content: `\u672A\u77E5\u547D\u4EE4\uFF1A${trimmed}\u3002\u8F93\u5165 /help \u67E5\u770B\u3002` }
|
|
@@ -569,60 +1592,220 @@ function ChatSession({ providerCount, toolCount, verbose, onLaunchGame, onLaunch
|
|
|
569
1592
|
setInput("");
|
|
570
1593
|
return;
|
|
571
1594
|
}
|
|
572
|
-
|
|
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) => [
|
|
573
1605
|
...prev,
|
|
574
|
-
{ role: "user", content: trimmed }
|
|
575
|
-
{ 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 }
|
|
576
1607
|
]);
|
|
577
1608
|
setInput("");
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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) => {
|
|
582
1710
|
const colorIndex = (i + offset) % CYBER_PALETTE.length;
|
|
583
|
-
return /* @__PURE__ */
|
|
1711
|
+
return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
|
|
584
1712
|
}) }),
|
|
585
|
-
/* @__PURE__ */
|
|
586
|
-
/* @__PURE__ */
|
|
1713
|
+
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", justifyContent: "center", children: [
|
|
1714
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "#00ff41", children: [
|
|
587
1715
|
" \u2714 ",
|
|
588
1716
|
"\u5DF2\u52A0\u8F7D ",
|
|
589
1717
|
providerCount,
|
|
590
1718
|
" \u4E2A Provider"
|
|
591
1719
|
] }),
|
|
592
|
-
/* @__PURE__ */
|
|
1720
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
|
|
593
1721
|
" \u2139 ",
|
|
594
1722
|
"\u5DF2\u5C31\u7EEA ",
|
|
595
1723
|
toolCount,
|
|
596
1724
|
" \u4E2A\u5DE5\u5177"
|
|
597
1725
|
] }),
|
|
598
|
-
|
|
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
|
|
1731
|
+
] }),
|
|
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
|
|
599
1747
|
] })
|
|
600
1748
|
] }),
|
|
601
|
-
/* @__PURE__ */
|
|
602
|
-
/* @__PURE__ */
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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(
|
|
608
1790
|
TextInput,
|
|
609
1791
|
{
|
|
610
1792
|
value: input,
|
|
611
1793
|
onChange: setInput,
|
|
612
1794
|
onSubmit: handleSubmit,
|
|
613
|
-
placeholder: "
|
|
1795
|
+
placeholder: ""
|
|
614
1796
|
}
|
|
615
1797
|
) })
|
|
616
1798
|
] }),
|
|
617
|
-
/* @__PURE__ */
|
|
618
|
-
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" }) })
|
|
619
1802
|
] });
|
|
620
1803
|
}
|
|
621
1804
|
|
|
622
1805
|
// src/ui/GamePicker.tsx
|
|
623
|
-
import { Box as
|
|
1806
|
+
import { Box as Box6, Text as Text7, useInput as useInput2 } from "ink";
|
|
624
1807
|
import { useState as useState4, useCallback as useCallback3 } from "react";
|
|
625
|
-
import { jsx as
|
|
1808
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
626
1809
|
function GamePicker({ games, onSelect, onExit, onBackToChat }) {
|
|
627
1810
|
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
628
1811
|
const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
|
|
@@ -650,18 +1833,18 @@ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
|
|
|
650
1833
|
[games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
|
|
651
1834
|
)
|
|
652
1835
|
);
|
|
653
|
-
return /* @__PURE__ */
|
|
654
|
-
/* @__PURE__ */
|
|
655
|
-
/* @__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) => {
|
|
656
1839
|
const isSelected = index === selectedIndex;
|
|
657
|
-
return /* @__PURE__ */
|
|
658
|
-
/* @__PURE__ */
|
|
659
|
-
/* @__PURE__ */
|
|
660
|
-
/* @__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 }) })
|
|
661
1844
|
] }, game.id);
|
|
662
1845
|
}) }),
|
|
663
|
-
/* @__PURE__ */
|
|
664
|
-
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" }) })
|
|
665
1848
|
] });
|
|
666
1849
|
}
|
|
667
1850
|
|
|
@@ -678,9 +1861,9 @@ function listGames() {
|
|
|
678
1861
|
}
|
|
679
1862
|
|
|
680
1863
|
// src/game/brick-breaker/index.tsx
|
|
681
|
-
import { Box as
|
|
682
|
-
import { useState as useState5, useEffect as useEffect4, useRef as
|
|
683
|
-
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";
|
|
684
1867
|
var GAME_WIDTH = 40;
|
|
685
1868
|
var GAME_HEIGHT = 18;
|
|
686
1869
|
var PADDLE_WIDTH = 9;
|
|
@@ -813,9 +1996,9 @@ function buildBoard(state) {
|
|
|
813
1996
|
function BrickBreakerGame({ onExit: _onExit }) {
|
|
814
1997
|
const [initialLevel, setInitialLevel] = useState5(1);
|
|
815
1998
|
const [selectingLevel, setSelectingLevel] = useState5(true);
|
|
816
|
-
const stateRef =
|
|
1999
|
+
const stateRef = useRef3(createInitialState(initialLevel));
|
|
817
2000
|
const [tick, setTick] = useState5(0);
|
|
818
|
-
const onExitRef =
|
|
2001
|
+
const onExitRef = useRef3(_onExit);
|
|
819
2002
|
onExitRef.current = _onExit;
|
|
820
2003
|
useEffect4(() => {
|
|
821
2004
|
if (selectingLevel) return;
|
|
@@ -870,49 +2053,49 @@ function BrickBreakerGame({ onExit: _onExit }) {
|
|
|
870
2053
|
const board = buildBoard(s);
|
|
871
2054
|
const def = getLevel(s.level);
|
|
872
2055
|
void tick;
|
|
873
|
-
return /* @__PURE__ */
|
|
874
|
-
/* @__PURE__ */
|
|
875
|
-
/* @__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: [
|
|
876
2059
|
"\u5173\u5361 ",
|
|
877
2060
|
s.level,
|
|
878
2061
|
": ",
|
|
879
|
-
/* @__PURE__ */
|
|
2062
|
+
/* @__PURE__ */ jsx7(Text8, { color: "cyan", children: def.desc })
|
|
880
2063
|
] }) }),
|
|
881
|
-
/* @__PURE__ */
|
|
2064
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
882
2065
|
"\u5206\u6570: ",
|
|
883
|
-
/* @__PURE__ */
|
|
2066
|
+
/* @__PURE__ */ jsx7(Text8, { color: "yellow", children: String(s.score).padStart(3, "0") })
|
|
884
2067
|
] }) }),
|
|
885
|
-
/* @__PURE__ */
|
|
2068
|
+
/* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
886
2069
|
"\u751F\u547D: ",
|
|
887
|
-
/* @__PURE__ */
|
|
2070
|
+
/* @__PURE__ */ jsx7(Text8, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
|
|
888
2071
|
] }) }),
|
|
889
|
-
/* @__PURE__ */
|
|
2072
|
+
/* @__PURE__ */ jsx7(Box7, { width: 10, children: /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
890
2073
|
"\u7816\u5757: ",
|
|
891
|
-
/* @__PURE__ */
|
|
2074
|
+
/* @__PURE__ */ jsx7(Text8, { color: "cyan", children: aliveCount })
|
|
892
2075
|
] }) }),
|
|
893
|
-
/* @__PURE__ */
|
|
2076
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: s.paused ? "gray" : "green", children: [
|
|
894
2077
|
"[",
|
|
895
2078
|
s.paused ? "\u6682\u505C" : "\u8FD0\u884C\u4E2D",
|
|
896
2079
|
"]"
|
|
897
2080
|
] }) })
|
|
898
2081
|
] }),
|
|
899
|
-
/* @__PURE__ */
|
|
900
|
-
/* @__PURE__ */
|
|
2082
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
2083
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
901
2084
|
"\u250C",
|
|
902
2085
|
"\u2500".repeat(GAME_WIDTH),
|
|
903
2086
|
"\u2510"
|
|
904
2087
|
] }),
|
|
905
|
-
/* @__PURE__ */
|
|
906
|
-
/* @__PURE__ */
|
|
2088
|
+
/* @__PURE__ */ jsx7(Text8, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
|
|
2089
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
907
2090
|
"\u2514",
|
|
908
2091
|
"\u2500".repeat(GAME_WIDTH),
|
|
909
2092
|
"\u2518"
|
|
910
2093
|
] })
|
|
911
2094
|
] }),
|
|
912
|
-
selectingLevel && /* @__PURE__ */
|
|
913
|
-
/* @__PURE__ */
|
|
914
|
-
/* @__PURE__ */
|
|
915
|
-
/* @__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) }),
|
|
916
2099
|
". ",
|
|
917
2100
|
lv.desc,
|
|
918
2101
|
" (",
|
|
@@ -921,16 +2104,16 @@ function BrickBreakerGame({ onExit: _onExit }) {
|
|
|
921
2104
|
lv.cols,
|
|
922
2105
|
")"
|
|
923
2106
|
] }) }, i)) }),
|
|
924
|
-
/* @__PURE__ */
|
|
2107
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
|
|
925
2108
|
] }),
|
|
926
|
-
!selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */
|
|
927
|
-
/* @__PURE__ */
|
|
928
|
-
/* @__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: [
|
|
929
2112
|
" \u5206\u6570: ",
|
|
930
|
-
/* @__PURE__ */
|
|
2113
|
+
/* @__PURE__ */ jsx7(Text8, { color: "yellow", children: s.score })
|
|
931
2114
|
] })
|
|
932
2115
|
] }),
|
|
933
|
-
!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" }) })
|
|
934
2117
|
] });
|
|
935
2118
|
}
|
|
936
2119
|
var brick_breaker_default = {
|
|
@@ -940,7 +2123,7 @@ var brick_breaker_default = {
|
|
|
940
2123
|
play: async () => {
|
|
941
2124
|
await new Promise((resolve) => {
|
|
942
2125
|
const { unmount } = render2(
|
|
943
|
-
/* @__PURE__ */
|
|
2126
|
+
/* @__PURE__ */ jsx7(BrickBreakerGame, { onExit: () => {
|
|
944
2127
|
unmount();
|
|
945
2128
|
resolve();
|
|
946
2129
|
} })
|
|
@@ -950,9 +2133,9 @@ var brick_breaker_default = {
|
|
|
950
2133
|
};
|
|
951
2134
|
|
|
952
2135
|
// src/game/coder-check/index.tsx
|
|
953
|
-
import { Box as
|
|
954
|
-
import { useState as useState6, useEffect as useEffect5, useRef as
|
|
955
|
-
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";
|
|
956
2139
|
var GAME_W = 66;
|
|
957
2140
|
var GAME_H = 20;
|
|
958
2141
|
var SCORE_H = 6;
|
|
@@ -1343,10 +2526,10 @@ function buildGameView(s, scoreLines, scoreColor, message) {
|
|
|
1343
2526
|
return rows;
|
|
1344
2527
|
}
|
|
1345
2528
|
function CoderCheck({ onExit: _onExit }) {
|
|
1346
|
-
const stateRef =
|
|
2529
|
+
const stateRef = useRef4(createInitialState2());
|
|
1347
2530
|
const [tick, setTick] = useState6(0);
|
|
1348
2531
|
const [colorOffset, setColorOffset] = useState6(0);
|
|
1349
|
-
const onExitRef =
|
|
2532
|
+
const onExitRef = useRef4(_onExit);
|
|
1350
2533
|
onExitRef.current = _onExit;
|
|
1351
2534
|
useEffect5(() => {
|
|
1352
2535
|
const timer = setInterval(() => {
|
|
@@ -1423,44 +2606,44 @@ function CoderCheck({ onExit: _onExit }) {
|
|
|
1423
2606
|
const scoreLines = buildScoreLines(scoreStr);
|
|
1424
2607
|
const view = buildGameView(s, scoreLines, scoreColor, s.message);
|
|
1425
2608
|
void tick;
|
|
1426
|
-
return /* @__PURE__ */
|
|
1427
|
-
/* @__PURE__ */
|
|
2609
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
|
|
2610
|
+
/* @__PURE__ */ jsx8(Box8, { flexDirection: "row", children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1428
2611
|
"\u751F\u547D ",
|
|
1429
|
-
/* @__PURE__ */
|
|
2612
|
+
/* @__PURE__ */ jsx8(Text9, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
|
|
1430
2613
|
" ",
|
|
1431
2614
|
"\u901F\u5EA6 ",
|
|
1432
|
-
/* @__PURE__ */
|
|
2615
|
+
/* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
|
|
1433
2616
|
"Lv.",
|
|
1434
2617
|
Math.floor(s.speed * 10)
|
|
1435
2618
|
] })
|
|
1436
2619
|
] }) }),
|
|
1437
|
-
!s.gameOver && s.target && /* @__PURE__ */
|
|
2620
|
+
!s.gameOver && s.target && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1438
2621
|
"\u6253\u5B57: ",
|
|
1439
|
-
/* @__PURE__ */
|
|
1440
|
-
/* @__PURE__ */
|
|
2622
|
+
/* @__PURE__ */ jsx8(Text9, { color: "green", children: s.typed }),
|
|
2623
|
+
/* @__PURE__ */ jsx8(Text9, { color: "white", children: s.target.slice(s.typed.length) })
|
|
1441
2624
|
] }) }),
|
|
1442
|
-
/* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
2625
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
2626
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1444
2627
|
"\u250C",
|
|
1445
2628
|
"\u2500".repeat(GAME_W),
|
|
1446
2629
|
"\u2510"
|
|
1447
2630
|
] }),
|
|
1448
|
-
view.map((row, i) => /* @__PURE__ */
|
|
1449
|
-
/* @__PURE__ */
|
|
2631
|
+
view.map((row, i) => /* @__PURE__ */ jsx8(Text9, { children: `\u2502${row}\u2502` }, i)),
|
|
2632
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
1450
2633
|
"\u2514",
|
|
1451
2634
|
"\u2500".repeat(GAME_W),
|
|
1452
2635
|
"\u2518"
|
|
1453
2636
|
] })
|
|
1454
2637
|
] }),
|
|
1455
|
-
s.gameOver && /* @__PURE__ */
|
|
1456
|
-
/* @__PURE__ */
|
|
1457
|
-
/* @__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: [
|
|
1458
2641
|
" \u5F97\u5206: ",
|
|
1459
|
-
/* @__PURE__ */
|
|
2642
|
+
/* @__PURE__ */ jsx8(Text9, { color: "yellow", children: s.score }),
|
|
1460
2643
|
" r \u91CD\u5F00 q \u9000\u51FA"
|
|
1461
2644
|
] })
|
|
1462
2645
|
] }),
|
|
1463
|
-
/* @__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" }) })
|
|
1464
2647
|
] });
|
|
1465
2648
|
}
|
|
1466
2649
|
var coder_check_default = {
|
|
@@ -1470,7 +2653,7 @@ var coder_check_default = {
|
|
|
1470
2653
|
play: async () => {
|
|
1471
2654
|
await new Promise((resolve) => {
|
|
1472
2655
|
const { unmount } = render3(
|
|
1473
|
-
/* @__PURE__ */
|
|
2656
|
+
/* @__PURE__ */ jsx8(CoderCheck, { onExit: () => {
|
|
1474
2657
|
unmount();
|
|
1475
2658
|
resolve();
|
|
1476
2659
|
} })
|
|
@@ -1491,10 +2674,10 @@ import { render as render4 } from "ink";
|
|
|
1491
2674
|
import chalk3 from "chalk";
|
|
1492
2675
|
|
|
1493
2676
|
// src/stock/StockList.tsx
|
|
1494
|
-
import { Box as
|
|
2677
|
+
import { Box as Box9, Text as Text10, useInput as useInput5 } from "ink";
|
|
1495
2678
|
import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6 } from "react";
|
|
1496
2679
|
import asciichart from "asciichart";
|
|
1497
|
-
import { jsx as
|
|
2680
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1498
2681
|
var MINUTE_API = "https://web.ifzq.gtimg.cn/appstock/app/minute/query?code={code}&r=0.1";
|
|
1499
2682
|
function normalizeApiCode(code) {
|
|
1500
2683
|
if (code.startsWith("sh") || code.startsWith("sz")) return code;
|
|
@@ -1691,57 +2874,57 @@ function StockList({ codes, onExit, onBackToChat }) {
|
|
|
1691
2874
|
);
|
|
1692
2875
|
if (detailView) {
|
|
1693
2876
|
if (detailLoading) {
|
|
1694
|
-
return /* @__PURE__ */
|
|
2877
|
+
return /* @__PURE__ */ jsx9(Box9, { paddingLeft: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
|
|
1695
2878
|
}
|
|
1696
2879
|
return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown, currentTime);
|
|
1697
2880
|
}
|
|
1698
|
-
return /* @__PURE__ */
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
/* @__PURE__ */
|
|
1701
|
-
/* @__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: [
|
|
1702
2885
|
" \u{1F550} ",
|
|
1703
2886
|
currentTime
|
|
1704
2887
|
] }),
|
|
1705
|
-
/* @__PURE__ */
|
|
2888
|
+
/* @__PURE__ */ jsx9(Text10, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
|
|
1706
2889
|
] }),
|
|
1707
|
-
/* @__PURE__ */
|
|
1708
|
-
/* @__PURE__ */
|
|
1709
|
-
/* @__PURE__ */
|
|
1710
|
-
/* @__PURE__ */
|
|
1711
|
-
/* @__PURE__ */
|
|
1712
|
-
/* @__PURE__ */
|
|
1713
|
-
/* @__PURE__ */
|
|
1714
|
-
/* @__PURE__ */
|
|
1715
|
-
/* @__PURE__ */
|
|
1716
|
-
/* @__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" }) })
|
|
1717
2900
|
] }),
|
|
1718
|
-
/* @__PURE__ */
|
|
1719
|
-
/* @__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) => {
|
|
1720
2903
|
const isSelected = index === selectedIndex;
|
|
1721
2904
|
const isUp = stock.changePercent >= 0;
|
|
1722
2905
|
const color = isUp ? "#ff1493" : "#00ff41";
|
|
1723
|
-
return /* @__PURE__ */
|
|
1724
|
-
/* @__PURE__ */
|
|
1725
|
-
/* @__PURE__ */
|
|
1726
|
-
/* @__PURE__ */
|
|
1727
|
-
/* @__PURE__ */
|
|
1728
|
-
/* @__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: [
|
|
1729
2912
|
isUp ? "+" : "",
|
|
1730
2913
|
stock.changePercent.toFixed(2),
|
|
1731
2914
|
"%"
|
|
1732
2915
|
] }) }),
|
|
1733
|
-
/* @__PURE__ */
|
|
2916
|
+
/* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
|
|
1734
2917
|
isUp ? "+" : "",
|
|
1735
2918
|
stock.changeAmount.toFixed(3)
|
|
1736
2919
|
] }) }),
|
|
1737
|
-
/* @__PURE__ */
|
|
1738
|
-
/* @__PURE__ */
|
|
1739
|
-
/* @__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) }) })
|
|
1740
2923
|
] }, stock.code);
|
|
1741
2924
|
}) }),
|
|
1742
|
-
/* @__PURE__ */
|
|
1743
|
-
/* @__PURE__ */
|
|
1744
|
-
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" }) })
|
|
1745
2928
|
] });
|
|
1746
2929
|
}
|
|
1747
2930
|
function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
|
|
@@ -1759,33 +2942,33 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
|
|
|
1759
2942
|
raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
|
|
1760
2943
|
chartLines = raw.split("\n");
|
|
1761
2944
|
}
|
|
1762
|
-
return /* @__PURE__ */
|
|
1763
|
-
/* @__PURE__ */
|
|
1764
|
-
/* @__PURE__ */
|
|
1765
|
-
/* @__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: [
|
|
1766
2949
|
" \u{1F4CA} ",
|
|
1767
2950
|
stock.name,
|
|
1768
2951
|
" "
|
|
1769
2952
|
] }),
|
|
1770
|
-
/* @__PURE__ */
|
|
1771
|
-
currentTime && /* @__PURE__ */
|
|
2953
|
+
/* @__PURE__ */ jsx9(Text10, { dimColor: true, children: stock.code }),
|
|
2954
|
+
currentTime && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
|
|
1772
2955
|
" \u{1F550} ",
|
|
1773
2956
|
currentTime
|
|
1774
2957
|
] })
|
|
1775
2958
|
] }),
|
|
1776
|
-
/* @__PURE__ */
|
|
2959
|
+
/* @__PURE__ */ jsx9(Text10, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
|
|
1777
2960
|
] }),
|
|
1778
|
-
/* @__PURE__ */
|
|
1779
|
-
/* @__PURE__ */
|
|
1780
|
-
/* @__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: [
|
|
1781
2964
|
arrow,
|
|
1782
2965
|
" ",
|
|
1783
2966
|
formatPrice(stock.price)
|
|
1784
2967
|
] }) })
|
|
1785
2968
|
] }),
|
|
1786
|
-
/* @__PURE__ */
|
|
1787
|
-
/* @__PURE__ */
|
|
1788
|
-
/* @__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: [
|
|
1789
2972
|
isUp ? "+" : "",
|
|
1790
2973
|
stock.changePercent.toFixed(2),
|
|
1791
2974
|
"%",
|
|
@@ -1794,13 +2977,13 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
|
|
|
1794
2977
|
stock.changeAmount.toFixed(3)
|
|
1795
2978
|
] }) })
|
|
1796
2979
|
] }),
|
|
1797
|
-
chartLines.length > 0 && /* @__PURE__ */
|
|
1798
|
-
/* @__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" }) })
|
|
1799
2982
|
] });
|
|
1800
2983
|
}
|
|
1801
2984
|
|
|
1802
2985
|
// src/cli/index.tsx
|
|
1803
|
-
import { jsx as
|
|
2986
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1804
2987
|
var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
|
|
1805
2988
|
function createCli() {
|
|
1806
2989
|
const program2 = new Command();
|
|
@@ -1826,34 +3009,46 @@ function createCli() {
|
|
|
1826
3009
|
const result = await loadAndValidate();
|
|
1827
3010
|
ctx = { ...ctx, config: result.config };
|
|
1828
3011
|
}
|
|
1829
|
-
|
|
3012
|
+
const costTracker = new CostTracker({
|
|
3013
|
+
budgetLimit: ctx?.config.budgetLimit ?? 0,
|
|
3014
|
+
tokenBudgetLimit: ctx?.config.tokenBudgetLimit ?? 0
|
|
3015
|
+
});
|
|
3016
|
+
startChat(ctx, costTracker);
|
|
1830
3017
|
});
|
|
1831
|
-
function startChat(ctx) {
|
|
3018
|
+
function startChat(ctx, costTracker) {
|
|
3019
|
+
const defaultProvider = ctx?.config.providers.find(
|
|
3020
|
+
(p) => p.name === (ctx?.config.defaultProvider ?? "deepseek")
|
|
3021
|
+
);
|
|
3022
|
+
const model = defaultProvider?.model ?? "deepseek-v4-flash";
|
|
1832
3023
|
const chatApp = renderApp(
|
|
1833
|
-
/* @__PURE__ */
|
|
3024
|
+
/* @__PURE__ */ jsx10(
|
|
1834
3025
|
ChatSession,
|
|
1835
3026
|
{
|
|
1836
3027
|
providerCount: ctx?.config.providers.length ?? 1,
|
|
1837
3028
|
toolCount: ctx?.config.tools.length ?? 0,
|
|
1838
3029
|
verbose: ctx?.verbose ?? false,
|
|
3030
|
+
apiKey: defaultProvider?.apiKey,
|
|
3031
|
+
baseUrl: defaultProvider?.baseUrl ?? "https://api.deepseek.com",
|
|
3032
|
+
costTracker,
|
|
3033
|
+
model,
|
|
1839
3034
|
onLaunchGame: () => {
|
|
1840
3035
|
chatApp.unmount();
|
|
1841
3036
|
setImmediate(() => {
|
|
1842
3037
|
initGames();
|
|
1843
3038
|
const games = listGames();
|
|
1844
3039
|
const { unmount } = render4(
|
|
1845
|
-
/* @__PURE__ */
|
|
3040
|
+
/* @__PURE__ */ jsx10(
|
|
1846
3041
|
GamePicker,
|
|
1847
3042
|
{
|
|
1848
3043
|
games,
|
|
1849
3044
|
onSelect: async (game) => {
|
|
1850
3045
|
unmount();
|
|
1851
3046
|
await game.play();
|
|
1852
|
-
startChat(ctx);
|
|
3047
|
+
startChat(ctx, costTracker);
|
|
1853
3048
|
},
|
|
1854
3049
|
onBackToChat: () => {
|
|
1855
3050
|
unmount();
|
|
1856
|
-
setImmediate(() => startChat(ctx));
|
|
3051
|
+
setImmediate(() => startChat(ctx, costTracker));
|
|
1857
3052
|
}
|
|
1858
3053
|
}
|
|
1859
3054
|
),
|
|
@@ -1866,13 +3061,13 @@ function createCli() {
|
|
|
1866
3061
|
setImmediate(() => {
|
|
1867
3062
|
const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
|
|
1868
3063
|
const stockApp = renderApp(
|
|
1869
|
-
/* @__PURE__ */
|
|
3064
|
+
/* @__PURE__ */ jsx10(
|
|
1870
3065
|
StockList,
|
|
1871
3066
|
{
|
|
1872
3067
|
codes: defaultStockCodes,
|
|
1873
3068
|
onBackToChat: () => {
|
|
1874
3069
|
stockApp.unmount();
|
|
1875
|
-
setImmediate(() => startChat(ctx));
|
|
3070
|
+
setImmediate(() => startChat(ctx, costTracker));
|
|
1876
3071
|
},
|
|
1877
3072
|
onExit: () => process.exit(0)
|
|
1878
3073
|
}
|
|
@@ -1930,10 +3125,10 @@ compdef _dskcode_completion dskcode`);
|
|
|
1930
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) {
|
|
1931
3126
|
const ctx = this.dskcodeCtx;
|
|
1932
3127
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
1933
|
-
const globalConfigPath =
|
|
3128
|
+
const globalConfigPath = join3(home, ".dskcode", "settings.json");
|
|
1934
3129
|
let globalConfigHasStock = false;
|
|
1935
3130
|
try {
|
|
1936
|
-
const raw = await
|
|
3131
|
+
const raw = await readFile3(globalConfigPath, "utf-8");
|
|
1937
3132
|
const parsed = JSON.parse(raw);
|
|
1938
3133
|
const stock = parsed.stock;
|
|
1939
3134
|
globalConfigHasStock = Array.isArray(stock?.symbols) && stock.symbols.length > 0;
|
|
@@ -1953,7 +3148,7 @@ compdef _dskcode_completion dskcode`);
|
|
|
1953
3148
|
const freshResult = await loadAndValidate();
|
|
1954
3149
|
const codeList = codes && codes.length > 0 ? codes : freshResult.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
|
|
1955
3150
|
const app = renderApp(
|
|
1956
|
-
/* @__PURE__ */
|
|
3151
|
+
/* @__PURE__ */ jsx10(
|
|
1957
3152
|
StockList,
|
|
1958
3153
|
{
|
|
1959
3154
|
codes: codeList,
|
|
@@ -1982,7 +3177,7 @@ compdef _dskcode_completion dskcode`);
|
|
|
1982
3177
|
}
|
|
1983
3178
|
const selectedGame = await new Promise((resolve) => {
|
|
1984
3179
|
const { unmount } = render4(
|
|
1985
|
-
/* @__PURE__ */
|
|
3180
|
+
/* @__PURE__ */ jsx10(
|
|
1986
3181
|
GamePicker,
|
|
1987
3182
|
{
|
|
1988
3183
|
games,
|