dskcode 0.1.11 → 0.1.12

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/index.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  ModelNotSupportedError
5
5
  } from "./chunk-FODNQIT4.js";
6
6
  import {
7
+ SUPPORTED_MODELS,
7
8
  calculateCost,
8
9
  estimateTokens,
9
10
  getModelMeta,
@@ -303,6 +304,34 @@ async function saveApiKey(apiKey) {
303
304
  await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
304
305
  return configFile;
305
306
  }
307
+ async function saveModelConfig(model) {
308
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
309
+ const configDir = join(home, ".dskcode");
310
+ const configFile = join(configDir, "settings.json");
311
+ await mkdir(configDir, { recursive: true });
312
+ let configData;
313
+ try {
314
+ const raw = await readFile(configFile, "utf-8");
315
+ configData = JSON.parse(raw);
316
+ } catch {
317
+ configData = structuredClone(defaultConfig);
318
+ }
319
+ const providers = configData.providers ?? [];
320
+ const defaultProviderName = configData.defaultProvider ?? "deepseek";
321
+ const existing = providers.find((p) => p.name === defaultProviderName);
322
+ if (existing) {
323
+ existing.model = model;
324
+ } else {
325
+ providers.push({
326
+ name: defaultProviderName,
327
+ baseUrl: "https://api.deepseek.com",
328
+ model
329
+ });
330
+ }
331
+ configData.providers = providers;
332
+ await writeFile(configFile, JSON.stringify(configData, null, 2), "utf-8");
333
+ return configFile;
334
+ }
306
335
  async function saveStockConfig(symbols) {
307
336
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
308
337
  const configDir = join(home, ".dskcode");
@@ -1308,6 +1337,14 @@ var IDLE_GRADIENT_STOPS = [
1308
1337
  [189, 147, 249]
1309
1338
  // #bd93f9
1310
1339
  ];
1340
+ var CMD_TIP_GRADIENT_STOPS = [
1341
+ [255, 245, 180],
1342
+ // #fff5b4 浅柠黄
1343
+ [255, 210, 80],
1344
+ // #ffd250 暖黄
1345
+ [255, 150, 50]
1346
+ // #ff9632 橙色
1347
+ ];
1311
1348
  var STREAMING_GRADIENT_STOPS = [
1312
1349
  [255, 191, 0],
1313
1350
  // #ffbf00 琥珀金
@@ -1324,7 +1361,11 @@ var GRADIENT_ANIMATION = {
1324
1361
  /** 流式占位符动画:每帧相位步进 */
1325
1362
  streamingPhaseStep: 0.05,
1326
1363
  /** 流式占位符动画:帧间隔(ms) */
1327
- streamingInterval: 40
1364
+ streamingInterval: 40,
1365
+ /** 命令提示条动画:每帧相位步进 */
1366
+ cmdTipPhaseStep: 0.05,
1367
+ /** 命令提示条动画:帧间隔(ms) */
1368
+ cmdTipInterval: 40
1328
1369
  };
1329
1370
  var SKIP_CHARS = /* @__PURE__ */ new Set([" ", "~", ".", "\u3002", "\uFF01", "\u{1F447}"]);
1330
1371
  function lerpStopsToHex(t, stops) {
@@ -1351,7 +1392,7 @@ function getGradientColors(text, phase, stops) {
1351
1392
  }
1352
1393
 
1353
1394
  // src/ui/ChatSession.tsx
1354
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1395
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1355
1396
  var commandRegistry = /* @__PURE__ */ new Map();
1356
1397
  function registerCommand(name, cmd) {
1357
1398
  commandRegistry.set(name, cmd);
@@ -1374,6 +1415,7 @@ registerCommand("/help", {
1374
1415
  });
1375
1416
  registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
1376
1417
  registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.1.10" }) });
1418
+ registerCommand("/model", { desc: "\u5207\u6362\u6A21\u578B", handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /model \u8FDB\u5165\u9009\u62E9\u754C\u9762" }) });
1377
1419
  registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
1378
1420
  registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
1379
1421
  var STREAMING_PLACEHOLDERS = [
@@ -1388,7 +1430,7 @@ var IDLE_PLACEHOLDERS = [
1388
1430
  "\u60F3\u5E72\u5565\uFF1F\u76F4\u63A5\u8BF4~",
1389
1431
  "\u6765\u5427\uFF0C\u5429\u5490\u70B9\u5565",
1390
1432
  "\u968F\u65F6\u5F85\u547D...",
1391
- "\u6233\u8FD9\u91CC\u5F00\u804A \u{1F447}",
1433
+ "\u6233\u8FD9\u91CC\u5F00\u804A...",
1392
1434
  "\u7B49\u4F60\u5F00\u53E3...",
1393
1435
  "\u5C3D\u7BA1\u4F7F\u5524~"
1394
1436
  ];
@@ -1426,8 +1468,16 @@ function ChatSession({
1426
1468
  const [currentUsage, setCurrentUsage] = useState3(void 0);
1427
1469
  const [currentElapsed, setCurrentElapsed] = useState3(void 0);
1428
1470
  const [currentCost, setCurrentCost] = useState3(void 0);
1429
- const [currentModel, setCurrentModel] = useState3(void 0);
1471
+ const [activeModel, setActiveModel] = useState3(model);
1472
+ const [streamingModel, setStreamingModel] = useState3(void 0);
1430
1473
  const [streamError, setStreamError] = useState3(void 0);
1474
+ const cmdTips = Array.from(getRegisteredCommands()).filter(([name]) => name !== "/exit" && name !== "/quit").map(([name, cmd]) => ({ name, desc: cmd.desc }));
1475
+ const [cmdTipIndex, setCmdTipIndex] = useState3(0);
1476
+ const [cmdTipGradientColors, setCmdTipGradientColors] = useState3([]);
1477
+ const cmdTipPhaseRef = useRef2(0);
1478
+ const [selectingModel, setSelectingModel] = useState3(false);
1479
+ const [modelSelectIndex, setModelSelectIndex] = useState3(0);
1480
+ const modelOptions = ["deepseek-v4-flash", "deepseek-v4-pro"];
1431
1481
  const sessionRef = useRef2(null);
1432
1482
  const abortRef = useRef2(null);
1433
1483
  const currentContentRef = useRef2("");
@@ -1443,6 +1493,34 @@ function ChatSession({
1443
1493
  useInput(
1444
1494
  useCallback2(
1445
1495
  (_input, key) => {
1496
+ if (selectingModel) {
1497
+ if (key.upArrow) {
1498
+ setModelSelectIndex((prev) => (prev - 1 + modelOptions.length) % modelOptions.length);
1499
+ } else if (key.downArrow) {
1500
+ setModelSelectIndex((prev) => (prev + 1) % modelOptions.length);
1501
+ } else if (key.return) {
1502
+ const selected = modelOptions[modelSelectIndex];
1503
+ if (selected === activeModel) {
1504
+ setDisplayMessages((prev) => [
1505
+ ...prev,
1506
+ { role: "assistant", content: `\u5DF2\u7ECF\u5728\u4F7F\u7528 ${SUPPORTED_MODELS[selected].displayName}` }
1507
+ ]);
1508
+ } else {
1509
+ setActiveModel(selected);
1510
+ sessionRef.current?.reset();
1511
+ saveModelConfig(selected).catch(() => {
1512
+ });
1513
+ setDisplayMessages((prev) => [
1514
+ ...prev,
1515
+ { role: "assistant", content: `\u6A21\u578B\u5DF2\u5207\u6362\u4E3A ${SUPPORTED_MODELS[selected].displayName}\uFF08${selected}\uFF09` }
1516
+ ]);
1517
+ }
1518
+ setSelectingModel(false);
1519
+ } else if (key.escape) {
1520
+ setSelectingModel(false);
1521
+ }
1522
+ return;
1523
+ }
1446
1524
  if (key.ctrl && _input === "c") {
1447
1525
  if (isStreaming) {
1448
1526
  abortRef.current?.abort();
@@ -1455,9 +1533,31 @@ function ChatSession({
1455
1533
  setInput(_input);
1456
1534
  }
1457
1535
  },
1458
- [isStreaming, handleCtrlC, input]
1536
+ [selectingModel, modelSelectIndex, modelOptions, activeModel, isStreaming, handleCtrlC, input]
1459
1537
  )
1460
1538
  );
1539
+ useEffect3(() => {
1540
+ if (cmdTips.length <= 1) return;
1541
+ const timer = setInterval(() => {
1542
+ setCmdTipIndex((prev) => (prev + 1) % cmdTips.length);
1543
+ }, 5e3);
1544
+ return () => clearInterval(timer);
1545
+ }, [cmdTips.length]);
1546
+ useEffect3(() => {
1547
+ const tip = cmdTips[cmdTipIndex % cmdTips.length];
1548
+ if (!tip) {
1549
+ setCmdTipGradientColors([]);
1550
+ return;
1551
+ }
1552
+ const text = `${tip.name} ${tip.desc}`;
1553
+ cmdTipPhaseRef.current = 0;
1554
+ setCmdTipGradientColors(getGradientColors(text, 1, CMD_TIP_GRADIENT_STOPS));
1555
+ const interval = setInterval(() => {
1556
+ cmdTipPhaseRef.current = (cmdTipPhaseRef.current + GRADIENT_ANIMATION.cmdTipPhaseStep) % 1;
1557
+ setCmdTipGradientColors(getGradientColors(text, 1 - cmdTipPhaseRef.current, CMD_TIP_GRADIENT_STOPS));
1558
+ }, GRADIENT_ANIMATION.cmdTipInterval);
1559
+ return () => clearInterval(interval);
1560
+ }, [cmdTipIndex, cmdTips.length]);
1461
1561
  useEffect3(() => {
1462
1562
  const timer = setInterval(() => {
1463
1563
  setOffset((prev) => (prev + 1) % CYBER_PALETTE.length);
@@ -1470,7 +1570,7 @@ function ChatSession({
1470
1570
  name: "deepseek",
1471
1571
  apiKey,
1472
1572
  baseUrl,
1473
- model
1573
+ model: activeModel
1474
1574
  });
1475
1575
  const tracker = externalCostTracker ?? new CostTracker();
1476
1576
  const session = new Session(provider, [], tracker, {
@@ -1480,7 +1580,7 @@ function ChatSession({
1480
1580
  return () => {
1481
1581
  sessionRef.current = null;
1482
1582
  };
1483
- }, [apiKey, baseUrl, model, externalCostTracker]);
1583
+ }, [apiKey, baseUrl, activeModel, externalCostTracker]);
1484
1584
  useEffect3(() => {
1485
1585
  if (!apiKey || !baseUrl) return;
1486
1586
  let cancelled = false;
@@ -1530,10 +1630,10 @@ function ChatSession({
1530
1630
  return;
1531
1631
  }
1532
1632
  gradientPhaseRef.current = 0;
1533
- setGradientColors(getGradientColors(idlePlaceholder, 0, IDLE_GRADIENT_STOPS));
1633
+ setGradientColors(getGradientColors(idlePlaceholder, 1, IDLE_GRADIENT_STOPS));
1534
1634
  const interval = setInterval(() => {
1535
1635
  gradientPhaseRef.current = (gradientPhaseRef.current + GRADIENT_ANIMATION.idlePhaseStep) % 1;
1536
- setGradientColors(getGradientColors(idlePlaceholder, gradientPhaseRef.current, IDLE_GRADIENT_STOPS));
1636
+ setGradientColors(getGradientColors(idlePlaceholder, 1 - gradientPhaseRef.current, IDLE_GRADIENT_STOPS));
1537
1637
  }, GRADIENT_ANIMATION.idleInterval);
1538
1638
  return () => clearInterval(interval);
1539
1639
  }, [isStreaming, idlePlaceholder]);
@@ -1543,10 +1643,10 @@ function ChatSession({
1543
1643
  return;
1544
1644
  }
1545
1645
  streamingPhaseRef.current = 0;
1546
- setStreamingGradientColors(getGradientColors(streamingPlaceholder, 0, STREAMING_GRADIENT_STOPS));
1646
+ setStreamingGradientColors(getGradientColors(streamingPlaceholder, 1, STREAMING_GRADIENT_STOPS));
1547
1647
  const interval = setInterval(() => {
1548
1648
  streamingPhaseRef.current = (streamingPhaseRef.current + GRADIENT_ANIMATION.streamingPhaseStep) % 1;
1549
- setStreamingGradientColors(getGradientColors(streamingPlaceholder, streamingPhaseRef.current, STREAMING_GRADIENT_STOPS));
1649
+ setStreamingGradientColors(getGradientColors(streamingPlaceholder, 1 - streamingPhaseRef.current, STREAMING_GRADIENT_STOPS));
1550
1650
  }, GRADIENT_ANIMATION.streamingInterval);
1551
1651
  return () => clearInterval(interval);
1552
1652
  }, [isStreaming, streamingPlaceholder]);
@@ -1554,6 +1654,13 @@ function ChatSession({
1554
1654
  const trimmed = value.trim();
1555
1655
  if (!trimmed) return;
1556
1656
  if (trimmed.startsWith("/")) {
1657
+ if (trimmed.toLowerCase() === "/model") {
1658
+ const curIdx = modelOptions.indexOf(activeModel);
1659
+ setModelSelectIndex(curIdx >= 0 ? curIdx : 0);
1660
+ setSelectingModel(true);
1661
+ setInput("");
1662
+ return;
1663
+ }
1557
1664
  const cmd = commandRegistry.get(trimmed.toLowerCase());
1558
1665
  if (cmd) {
1559
1666
  const result = cmd.handler();
@@ -1613,7 +1720,7 @@ function ChatSession({
1613
1720
  setCurrentUsage(void 0);
1614
1721
  setCurrentElapsed(void 0);
1615
1722
  setCurrentCost(void 0);
1616
- setCurrentModel(void 0);
1723
+ setStreamingModel(void 0);
1617
1724
  setStreamError(void 0);
1618
1725
  currentContentRef.current = "";
1619
1726
  currentToolCallsRef.current = [];
@@ -1645,7 +1752,7 @@ function ChatSession({
1645
1752
  break;
1646
1753
  case "usage":
1647
1754
  setCurrentUsage(event.usage);
1648
- setCurrentModel(event.model);
1755
+ setStreamingModel(event.model);
1649
1756
  currentUsageRef.current = event.usage;
1650
1757
  currentModelRef.current = event.model;
1651
1758
  break;
@@ -1696,14 +1803,14 @@ function ChatSession({
1696
1803
  }
1697
1804
  }, [isStreaming, externalCostTracker]);
1698
1805
  useEffect3(() => {
1699
- if (currentUsage && currentModel && !currentCost) {
1806
+ if (currentUsage && streamingModel && !currentCost) {
1700
1807
  import("./models-NQGNKB4T.js").then(({ calculateCost: calculateCost2 }) => {
1701
- const cost = calculateCost2(currentUsage, currentModel);
1808
+ const cost = calculateCost2(currentUsage, streamingModel);
1702
1809
  setCurrentCost(cost.totalCost);
1703
1810
  currentCostRef.current = cost.totalCost;
1704
1811
  });
1705
1812
  }
1706
- }, [currentUsage, currentModel, currentCost]);
1813
+ }, [currentUsage, streamingModel, currentCost]);
1707
1814
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
1708
1815
  /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", marginBottom: 1, children: [
1709
1816
  /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
@@ -1725,8 +1832,17 @@ function ChatSession({
1725
1832
  ] }),
1726
1833
  /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
1727
1834
  " \u{1F527} \u6A21\u578B ",
1728
- model
1835
+ SUPPORTED_MODELS[activeModel]?.displayName ?? activeModel
1729
1836
  ] }),
1837
+ cmdTips.length > 0 && (() => {
1838
+ const tip = cmdTips[cmdTipIndex % cmdTips.length];
1839
+ if (!tip) return null;
1840
+ const text = `${tip.name} ${tip.desc}`;
1841
+ return /* @__PURE__ */ jsxs5(Text6, { children: [
1842
+ /* @__PURE__ */ jsx5(Text6, { color: "#808080", children: " \u{1F4A1} " }),
1843
+ cmdTipGradientColors.length > 0 ? text.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: cmdTipGradientColors[i] || void 0, children: ch }, i)) : /* @__PURE__ */ jsx5(Text6, { color: "#808080", children: text })
1844
+ ] });
1845
+ })(),
1730
1846
  verbose ? /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
1731
1847
  ] }),
1732
1848
  /* @__PURE__ */ jsxs5(Box5, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "flex-end", children: [
@@ -1783,20 +1899,54 @@ function ChatSession({
1783
1899
  streamError
1784
1900
  ] }) })
1785
1901
  ] }),
1786
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1787
- /* @__PURE__ */ jsxs5(Box5, { children: [
1788
- /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
1789
- /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: !input && !isStreaming && idlePlaceholder && gradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: idlePlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: gradientColors[i] ?? void 0, children: ch }, i)) }) : !input && isStreaming && streamingPlaceholder && streamingGradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: streamingPlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: streamingGradientColors[i] ?? void 0, children: ch }, i)) }) : /* @__PURE__ */ jsx5(
1790
- TextInput,
1791
- {
1792
- value: input,
1793
- onChange: setInput,
1794
- onSubmit: handleSubmit,
1795
- placeholder: ""
1796
- }
1797
- ) })
1902
+ selectingModel ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1903
+ /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
1904
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1905
+ /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ffff", children: "\u9009\u62E9\u6A21\u578B\uFF1A" }),
1906
+ modelOptions.map((id, i) => {
1907
+ const meta = SUPPORTED_MODELS[id];
1908
+ const isCurrent = id === activeModel;
1909
+ const isSelected = i === modelSelectIndex;
1910
+ const marker = isSelected ? " > " : " ";
1911
+ const suffix = isCurrent ? " (\u5F53\u524D)" : "";
1912
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
1913
+ /* @__PURE__ */ jsxs5(
1914
+ Text6,
1915
+ {
1916
+ color: isSelected ? "#00ff41" : void 0,
1917
+ bold: isSelected,
1918
+ children: [
1919
+ marker,
1920
+ meta.displayName,
1921
+ suffix
1922
+ ]
1923
+ }
1924
+ ),
1925
+ isSelected && /* @__PURE__ */ jsxs5(Text6, { color: "#808080", children: [
1926
+ " \u2014 ",
1927
+ id
1928
+ ] })
1929
+ ] }, id);
1930
+ }),
1931
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
1932
+ ] }),
1933
+ /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
1934
+ ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1935
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1936
+ /* @__PURE__ */ jsxs5(Box5, { children: [
1937
+ /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
1938
+ /* @__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(
1939
+ TextInput,
1940
+ {
1941
+ value: input,
1942
+ onChange: setInput,
1943
+ onSubmit: handleSubmit,
1944
+ placeholder: ""
1945
+ }
1946
+ ) })
1947
+ ] }),
1948
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) })
1798
1949
  ] }),
1799
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
1800
1950
  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
1951
  isStreaming && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "yellow", dimColor: true, children: " \u63D0\u793A\uFF1A\u6309 Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42" }) })
1802
1952
  ] });