miantiao.me 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/{app-UWAFKI3E.js → app-IW4THTCR.js} +574 -264
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# miantiao.me
|
|
2
2
|
|
|
3
|
-
在终端浏览面条的文章、项目和标签,也可以直接向 AI 助手提问。界面使用 assistant-ui Ink
|
|
3
|
+
在终端浏览面条的文章、项目和标签,也可以直接向 AI 助手提问。界面使用 assistant-ui Ink:铺满终端、底部输入框和独立滚动的内容区,与 [miantiao.me](https://miantiao.me) 的主题、欢迎页和会话块保持一致。
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npx miantiao.me
|
|
@@ -8,7 +8,7 @@ npx miantiao.me
|
|
|
8
8
|
|
|
9
9
|
需要 Node.js 22.14 或更新版本,以及交互式终端。文章、项目、首页、关于和搜索索引从官网读取;官网部署后,下一次启动、导航或 `/refresh` 就能读取更新内容。已打开的终端无需重装包。TUI 功能升级仍通过 npm 版本发布。
|
|
10
10
|
|
|
11
|
-
输入普通文字可向 AI
|
|
11
|
+
输入普通文字可向 AI 助手提问。输入 `/` 展开命令面板,可按命令名或中文说明筛选,用 ↑↓ 选择、Enter 确认、Esc 关闭。选择 `/search` 或 `/read` 后继续填写参数。斜杠命令在本地执行:
|
|
12
12
|
|
|
13
13
|
| 命令 | 用途 |
|
|
14
14
|
| ------------------------------------ | -------------------------- |
|
|
@@ -24,7 +24,7 @@ npx miantiao.me
|
|
|
24
24
|
| `/clear` | 清空 AI 对话上下文 |
|
|
25
25
|
| `/quit` | 退出 |
|
|
26
26
|
|
|
27
|
-
Tab
|
|
27
|
+
PgUp/PgDn 滚动正文,输入框始终固定在底部。Tab 在输入框和内容区之间切换;内容区按 `/` 可返回输入框打开命令面板。列表中用 ↑↓ 选择、Enter 打开,文章中用 ↑↓、Home、End 滚动。Esc 取消加载或停止回复,Ctrl+C 退出。编号对应当前页面的结果列表,可用于 `/read 1` 或 `/open 1`。`/clear` 清空 AI 会话。退出后恢复原终端画面。
|
|
28
28
|
|
|
29
29
|
支持 GrokNight、GrokDay、TokyoNight、RosePine Moon、Oscura Midnight 和 `auto`。自动模式参考终端的 `COLORFGBG`;未提供时使用 GrokNight。可用 `/theme light` 或 `/theme dark` 显式选择。
|
|
30
30
|
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
|
|
5
5
|
// packages/cli/src/app.tsx
|
|
6
6
|
import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
|
|
7
|
-
import { Box as
|
|
8
|
-
import {
|
|
7
|
+
import { Box as Box9, render, Text as Text9, useApp as useApp2, useInput as useInput6, useWindowSize as useWindowSize3 } from "ink";
|
|
8
|
+
import { useEffect as useEffect5, useState as useState6 } from "react";
|
|
9
9
|
|
|
10
10
|
// src/terminal/schema.ts
|
|
11
11
|
import { z } from "zod";
|
|
@@ -52,12 +52,22 @@ var terminalIndexSchema = z.object({
|
|
|
52
52
|
tags: z.array(z.object({ name: z.string(), path: z.string(), count: z.number().int().nonnegative() })),
|
|
53
53
|
searchIndexUrl: z.string()
|
|
54
54
|
});
|
|
55
|
+
var sectionSchema = z.discriminatedUnion("kind", [
|
|
56
|
+
z.object({
|
|
57
|
+
kind: z.literal("welcome"),
|
|
58
|
+
markdown: z.string(),
|
|
59
|
+
logo: z.string().optional(),
|
|
60
|
+
menu: z.array(linkSchema.extend({ command: z.string() })).optional()
|
|
61
|
+
}),
|
|
62
|
+
z.object({ kind: z.literal("turn"), prompt: z.string(), markdown: z.string() })
|
|
63
|
+
]);
|
|
55
64
|
var terminalDocumentSchema = z.object({
|
|
56
65
|
schemaVersion: z.literal(1),
|
|
57
66
|
revision: z.string().min(1),
|
|
58
67
|
path: z.string(),
|
|
59
68
|
title: z.string(),
|
|
60
|
-
markdown: z.string().min(1)
|
|
69
|
+
markdown: z.string().min(1),
|
|
70
|
+
sections: z.array(sectionSchema).optional()
|
|
61
71
|
});
|
|
62
72
|
|
|
63
73
|
// src/utils/search-core.ts
|
|
@@ -276,49 +286,12 @@ var TerminalClient = class {
|
|
|
276
286
|
};
|
|
277
287
|
|
|
278
288
|
// packages/cli/src/console.tsx
|
|
279
|
-
import {
|
|
280
|
-
|
|
281
|
-
LoadingPrimitive,
|
|
282
|
-
ThreadPrimitive as ThreadPrimitive2,
|
|
283
|
-
useAui,
|
|
284
|
-
useAuiState as useAuiState2
|
|
285
|
-
} from "@assistant-ui/react-ink";
|
|
286
|
-
import { Box as Box2, Static, Text as Text2, useFocus, useInput } from "ink";
|
|
287
|
-
import { useState } from "react";
|
|
288
|
-
|
|
289
|
-
// src/agent/commands.ts
|
|
290
|
-
var commands = [
|
|
291
|
-
{ id: "help", description: "\u67E5\u770B\u53EF\u7528\u547D\u4EE4" },
|
|
292
|
-
{ id: "theme", args: "[\u4E3B\u9898]", description: "\u5207\u6362\u4E3B\u9898\uFF0C\u4E0D\u5E26\u53C2\u6570\u65F6\u6253\u5F00\u9009\u62E9\u5668" },
|
|
293
|
-
{ id: "clear", description: "\u6E05\u7A7A\u5F53\u524D\u5BF9\u8BDD" },
|
|
294
|
-
{ id: "home", description: "\u6253\u5F00\u9996\u9875" },
|
|
295
|
-
{ id: "blog", description: "\u6D4F\u89C8\u5168\u90E8\u6587\u7AE0" },
|
|
296
|
-
{ id: "projects", description: "\u67E5\u770B\u6298\u817E\u7684\u9879\u76EE" },
|
|
297
|
-
{ id: "tags", description: "\u6309\u6807\u7B7E\u6D4F\u89C8\u6587\u7AE0" },
|
|
298
|
-
{ id: "search", args: "[\u5173\u952E\u8BCD]", description: "\u641C\u7D22\u6587\u7AE0" },
|
|
299
|
-
{ id: "about", description: "\u5173\u4E8E\u672C\u7AD9" },
|
|
300
|
-
{ id: "rss", description: "\u8BA2\u9605 RSS" }
|
|
301
|
-
];
|
|
302
|
-
var pageCommands = {
|
|
303
|
-
home: "/",
|
|
304
|
-
blog: "/blog/",
|
|
305
|
-
projects: "/projects/",
|
|
306
|
-
tags: "/tags/",
|
|
307
|
-
about: "/about/"
|
|
308
|
-
};
|
|
309
|
-
function parseCommand(input) {
|
|
310
|
-
const match = input.trim().match(/^\/(\S+)(?:\s+([\s\S]*))?$/);
|
|
311
|
-
if (!match)
|
|
312
|
-
return null;
|
|
313
|
-
return { name: match[1].toLowerCase(), args: (match[2] ?? "").trim() };
|
|
314
|
-
}
|
|
315
|
-
function getSearchPath(query) {
|
|
316
|
-
return query ? `/search/?q=${encodeURIComponent(query)}` : "/search/";
|
|
317
|
-
}
|
|
289
|
+
import { ThreadPrimitive as ThreadPrimitive2 } from "@assistant-ui/react-ink";
|
|
290
|
+
import { useRef as useRef5 } from "react";
|
|
318
291
|
|
|
319
292
|
// packages/cli/src/chat.tsx
|
|
320
293
|
import { ThreadPrimitive, useAuiState } from "@assistant-ui/react-ink";
|
|
321
|
-
import { Box, Text } from "ink";
|
|
294
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
322
295
|
|
|
323
296
|
// packages/cli/src/theme.tsx
|
|
324
297
|
import { createContext, useContext, useMemo } from "react";
|
|
@@ -423,43 +396,225 @@ function useTerminalStyle() {
|
|
|
423
396
|
if (!style) throw new Error("TerminalTheme is required");
|
|
424
397
|
return style;
|
|
425
398
|
}
|
|
426
|
-
function Markdown({ text }) {
|
|
399
|
+
function Markdown({ text, width: availableWidth }) {
|
|
427
400
|
const { width, markdownTheme, highlighter } = useTerminalStyle();
|
|
428
401
|
return /* @__PURE__ */ jsx(
|
|
429
402
|
MarkdownText,
|
|
430
403
|
{
|
|
431
404
|
text,
|
|
432
|
-
width: Math.max(10, width -
|
|
405
|
+
width: Math.max(10, availableWidth ?? width - 3),
|
|
433
406
|
theme: markdownTheme,
|
|
434
407
|
highlighter,
|
|
435
408
|
codeWrap: true,
|
|
409
|
+
hyperlinks: true,
|
|
436
410
|
tableTruncate: false
|
|
437
411
|
}
|
|
438
412
|
);
|
|
439
413
|
}
|
|
440
414
|
|
|
441
|
-
// packages/cli/src/
|
|
415
|
+
// packages/cli/src/blocks.tsx
|
|
416
|
+
import { Box as Box3, Text as Text3, useBoxMetrics as useBoxMetrics3 } from "ink";
|
|
417
|
+
import { useRef as useRef3 } from "react";
|
|
418
|
+
|
|
419
|
+
// packages/cli/src/page-list.tsx
|
|
420
|
+
import { Box as Box2, Text as Text2, useBoxMetrics as useBoxMetrics2, useInput as useInput2 } from "ink";
|
|
421
|
+
import { useEffect, useRef as useRef2, useState as useState2 } from "react";
|
|
422
|
+
|
|
423
|
+
// packages/cli/src/scroll-area.tsx
|
|
424
|
+
import {
|
|
425
|
+
Box,
|
|
426
|
+
Text,
|
|
427
|
+
measureElement,
|
|
428
|
+
useBoxMetrics,
|
|
429
|
+
useFocus,
|
|
430
|
+
useInput
|
|
431
|
+
} from "ink";
|
|
432
|
+
import {
|
|
433
|
+
createContext as createContext2,
|
|
434
|
+
useContext as useContext2,
|
|
435
|
+
useImperativeHandle,
|
|
436
|
+
useMemo as useMemo2,
|
|
437
|
+
useRef,
|
|
438
|
+
useState
|
|
439
|
+
} from "react";
|
|
442
440
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
441
|
+
var ScrollContext = createContext2(null);
|
|
442
|
+
function useScrollArea() {
|
|
443
|
+
const value = useContext2(ScrollContext);
|
|
444
|
+
if (!value) throw new Error("ScrollArea is required");
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
function ScrollArea({
|
|
448
|
+
children,
|
|
449
|
+
selection,
|
|
450
|
+
ref
|
|
451
|
+
}) {
|
|
452
|
+
const { colors } = useTerminalStyle();
|
|
453
|
+
const { isFocused } = useFocus({ id: "content" });
|
|
454
|
+
const viewport = useRef(null);
|
|
455
|
+
const content = useRef(null);
|
|
456
|
+
const { clientHeight: height } = useBoxMetrics(viewport);
|
|
457
|
+
const { height: contentHeight } = useBoxMetrics(content);
|
|
458
|
+
const [anchor, setAnchor] = useState(0);
|
|
459
|
+
const maximum = Math.max(0, contentHeight - height);
|
|
460
|
+
const offset = anchor === "end" ? maximum : Math.min(anchor, maximum);
|
|
461
|
+
const context = useMemo2(() => ({ focused: isFocused, height }), [isFocused, height]);
|
|
462
|
+
useImperativeHandle(ref, () => ({
|
|
463
|
+
end: () => setAnchor("end"),
|
|
464
|
+
reveal: (element) => {
|
|
465
|
+
if (!viewport.current) return false;
|
|
466
|
+
const item = measureElement(element);
|
|
467
|
+
const top = item.y - measureElement(viewport.current).y;
|
|
468
|
+
const visible = top >= offset && top < offset + height;
|
|
469
|
+
if (top < offset || item.height > height) setAnchor(Math.max(0, top));
|
|
470
|
+
else if (top + item.height > offset + height)
|
|
471
|
+
setAnchor(Math.min(maximum, top + item.height - height));
|
|
472
|
+
return visible;
|
|
473
|
+
}
|
|
474
|
+
}), [offset, height, maximum]);
|
|
475
|
+
useInput((_input, key) => {
|
|
476
|
+
let next = offset;
|
|
477
|
+
if (key.pageUp) next -= Math.max(1, height - 1);
|
|
478
|
+
else if (key.pageDown) next += Math.max(1, height - 1);
|
|
479
|
+
else if (isFocused && !selection && key.upArrow) next--;
|
|
480
|
+
else if (isFocused && !selection && key.downArrow) next++;
|
|
481
|
+
else if (isFocused && !selection && key.home) next = 0;
|
|
482
|
+
else if (isFocused && !selection && key.end) next = maximum;
|
|
483
|
+
else return;
|
|
484
|
+
setAnchor(next >= maximum ? "end" : Math.max(0, next));
|
|
485
|
+
});
|
|
486
|
+
const track = Math.max(0, height);
|
|
487
|
+
const thumbSize = Math.min(track, Math.max(1, Math.round(height * height / Math.max(1, contentHeight))));
|
|
488
|
+
const thumbTop = maximum > 0 ? Math.round(offset / maximum * (height - thumbSize)) : 0;
|
|
489
|
+
const before = Array(thumbTop).fill("\u2502").join("\n");
|
|
490
|
+
const thumb = Array(thumbSize).fill("\u2503").join("\n");
|
|
491
|
+
const after = Array(Math.max(0, track - thumbTop - thumbSize)).fill("\u2502").join("\n");
|
|
492
|
+
return /* @__PURE__ */ jsx2(ScrollContext, { value: context, children: /* @__PURE__ */ jsxs(Box, { flexGrow: 1, flexShrink: 1, minHeight: 1, marginX: 2, children: [
|
|
493
|
+
/* @__PURE__ */ jsx2(
|
|
494
|
+
Box,
|
|
495
|
+
{
|
|
496
|
+
ref: viewport,
|
|
497
|
+
flexDirection: "column",
|
|
498
|
+
flexGrow: 1,
|
|
499
|
+
flexShrink: 1,
|
|
500
|
+
overflow: "hidden",
|
|
501
|
+
contentOffsetY: offset,
|
|
502
|
+
children: /* @__PURE__ */ jsx2(Box, { ref: content, flexDirection: "column", flexShrink: 0, width: "100%", children })
|
|
503
|
+
}
|
|
504
|
+
),
|
|
505
|
+
/* @__PURE__ */ jsx2(Box, { width: 1, flexShrink: 0, "aria-hidden": true, children: /* @__PURE__ */ jsxs(Text, { color: colors.line, children: [
|
|
506
|
+
before,
|
|
507
|
+
before && "\n",
|
|
508
|
+
/* @__PURE__ */ jsx2(Text, { color: isFocused ? colors.accent : colors.muted, children: thumb }),
|
|
509
|
+
after && "\n",
|
|
510
|
+
after
|
|
511
|
+
] }) })
|
|
512
|
+
] }) });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// packages/cli/src/page-list.tsx
|
|
516
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
517
|
+
function PageList({ items, onSelect, scroll, compact = false }) {
|
|
518
|
+
const { focused } = useScrollArea();
|
|
519
|
+
const { colors } = useTerminalStyle();
|
|
520
|
+
const [selected, setSelected] = useState2(0);
|
|
521
|
+
const elements = useRef2(/* @__PURE__ */ new Map());
|
|
522
|
+
const container = useRef2(null);
|
|
523
|
+
const { hasMeasured } = useBoxMetrics2(container);
|
|
524
|
+
useEffect(() => {
|
|
525
|
+
const element = elements.current.get(items[selected].path);
|
|
526
|
+
if (focused && hasMeasured && element) scroll.current?.reveal(element);
|
|
527
|
+
}, [focused, hasMeasured, selected, items, scroll]);
|
|
528
|
+
useInput2((_input, key) => {
|
|
529
|
+
let next = selected;
|
|
530
|
+
if (key.upArrow) next--;
|
|
531
|
+
else if (key.downArrow) next++;
|
|
532
|
+
else if (key.home) next = 0;
|
|
533
|
+
else if (key.end) next = items.length - 1;
|
|
534
|
+
else if (key.return) {
|
|
535
|
+
const item = items[selected];
|
|
536
|
+
const element = elements.current.get(item.path);
|
|
537
|
+
if (element && scroll.current?.reveal(element) === false) return;
|
|
538
|
+
onSelect(item.path, item.external);
|
|
539
|
+
return;
|
|
540
|
+
} else return;
|
|
541
|
+
next = Math.max(0, Math.min(items.length - 1, next));
|
|
542
|
+
setSelected(next);
|
|
543
|
+
}, { isActive: focused });
|
|
544
|
+
return /* @__PURE__ */ jsx3(Box2, { ref: container, flexDirection: "column", marginTop: 1, children: items.map((item, index) => {
|
|
545
|
+
const active = focused && index === selected;
|
|
546
|
+
return /* @__PURE__ */ jsxs2(
|
|
547
|
+
Box2,
|
|
548
|
+
{
|
|
549
|
+
flexDirection: compact ? "row" : "column",
|
|
550
|
+
justifyContent: compact ? "space-between" : void 0,
|
|
551
|
+
marginBottom: compact ? 0 : 1,
|
|
552
|
+
backgroundColor: active ? colors.band : void 0,
|
|
553
|
+
ref: (element) => {
|
|
554
|
+
if (element) elements.current.set(item.path, element);
|
|
555
|
+
else elements.current.delete(item.path);
|
|
556
|
+
},
|
|
557
|
+
children: [
|
|
558
|
+
/* @__PURE__ */ jsxs2(Text2, { color: active ? colors.accent : colors.fg, bold: active, children: [
|
|
559
|
+
active ? "\u25B8" : compact ? " " : "\u2022",
|
|
560
|
+
" ",
|
|
561
|
+
compact ? "" : `${index + 1}. `,
|
|
562
|
+
cleanText(item.title)
|
|
563
|
+
] }),
|
|
564
|
+
item.description && /* @__PURE__ */ jsx3(Text2, { color: colors.muted, children: cleanText(item.description) })
|
|
565
|
+
]
|
|
566
|
+
},
|
|
567
|
+
item.path
|
|
568
|
+
);
|
|
569
|
+
}) });
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// packages/cli/src/blocks.tsx
|
|
573
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
574
|
+
function Prompt({ text, command = false }) {
|
|
575
|
+
const { colors } = useTerminalStyle();
|
|
576
|
+
return /* @__PURE__ */ jsxs3(Box3, { backgroundColor: colors.band, paddingX: 2, children: [
|
|
577
|
+
/* @__PURE__ */ jsx4(Text3, { color: colors.prompt, children: "\u276F " }),
|
|
578
|
+
/* @__PURE__ */ jsx4(Text3, { color: command ? colors.skill : colors.fg, children: cleanText(text) })
|
|
579
|
+
] });
|
|
580
|
+
}
|
|
581
|
+
function Welcome({ section, ...selection }) {
|
|
582
|
+
const { colors, width } = useTerminalStyle();
|
|
583
|
+
const content = useRef3(null);
|
|
584
|
+
const { clientWidth } = useBoxMetrics3(content);
|
|
585
|
+
return /* @__PURE__ */ jsxs3(Box3, { borderStyle: "round", borderColor: colors.line, paddingX: 2, paddingY: 1, gap: 4, children: [
|
|
586
|
+
section.logo && width >= 90 && /* @__PURE__ */ jsx4(Text3, { color: colors.skill, children: cleanText(section.logo) }),
|
|
587
|
+
/* @__PURE__ */ jsxs3(Box3, { ref: content, flexDirection: "column", flexGrow: 1, flexShrink: 1, minWidth: 0, children: [
|
|
588
|
+
/* @__PURE__ */ jsx4(Markdown, { text: section.markdown, width: clientWidth || width - 6 }),
|
|
589
|
+
section.menu && selection.items.length > 0 && /* @__PURE__ */ jsx4(PageList, { ...selection, compact: true })
|
|
590
|
+
] })
|
|
591
|
+
] });
|
|
592
|
+
}
|
|
593
|
+
function HomeSections({ page, ...selection }) {
|
|
594
|
+
return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", gap: 1, children: page.sections?.map((section) => section.kind === "welcome" ? /* @__PURE__ */ jsx4(Welcome, { section, items: page.items, ...selection }, "welcome") : /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, children: [
|
|
595
|
+
/* @__PURE__ */ jsx4(Prompt, { text: section.prompt }),
|
|
596
|
+
/* @__PURE__ */ jsx4(Box3, { flexDirection: "column", paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx4(Markdown, { text: section.markdown }) })
|
|
597
|
+
] }, section.prompt)) });
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// packages/cli/src/chat.tsx
|
|
601
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
443
602
|
var toolTitles = { set_theme: "Theme", open_page: "Open" };
|
|
444
603
|
function MessageBody({ message }) {
|
|
445
604
|
const { colors } = useTerminalStyle();
|
|
446
605
|
if (message.role === "user") {
|
|
447
|
-
return /* @__PURE__ */
|
|
448
|
-
"\u276F",
|
|
449
|
-
" ",
|
|
450
|
-
message.content.filter((part) => part.type === "text").map((part) => cleanText(part.text)).join("\n")
|
|
451
|
-
] }) });
|
|
606
|
+
return /* @__PURE__ */ jsx5(Box4, { marginY: 1, children: /* @__PURE__ */ jsx5(Prompt, { text: message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n") }) });
|
|
452
607
|
}
|
|
453
608
|
if (message.role !== "assistant") return null;
|
|
454
|
-
return /* @__PURE__ */
|
|
609
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
|
|
455
610
|
message.content.map((part, index) => {
|
|
456
611
|
if (part.type === "text")
|
|
457
|
-
return /* @__PURE__ */
|
|
612
|
+
return /* @__PURE__ */ jsx5(Markdown, { text: part.text }, index);
|
|
458
613
|
if (part.type === "reasoning") {
|
|
459
614
|
const label = message.status.type === "running" ? "\u601D\u8003\u4E2D\u2026" : "\u601D\u8003";
|
|
460
615
|
return (
|
|
461
616
|
// eslint-disable-next-line react-doctor/no-array-index-as-key
|
|
462
|
-
/* @__PURE__ */
|
|
617
|
+
/* @__PURE__ */ jsxs4(Text4, { color: colors.muted, children: [
|
|
463
618
|
"\u25C6 ",
|
|
464
619
|
label
|
|
465
620
|
] }, index)
|
|
@@ -467,7 +622,7 @@ function MessageBody({ message }) {
|
|
|
467
622
|
}
|
|
468
623
|
if (part.type === "tool-call") {
|
|
469
624
|
const title = toolTitles[part.toolName] ?? part.toolName;
|
|
470
|
-
return /* @__PURE__ */
|
|
625
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: colors.skill, children: [
|
|
471
626
|
"\u25C6 ",
|
|
472
627
|
cleanText(title),
|
|
473
628
|
" ",
|
|
@@ -476,144 +631,346 @@ function MessageBody({ message }) {
|
|
|
476
631
|
}
|
|
477
632
|
return null;
|
|
478
633
|
}),
|
|
479
|
-
message.status.type === "incomplete" && message.status.reason === "error" && /* @__PURE__ */
|
|
634
|
+
message.status.type === "incomplete" && message.status.reason === "error" && /* @__PURE__ */ jsx5(Text4, { color: colors.err, children: "\u25C6 AI \u6682\u65F6\u65E0\u6CD5\u56DE\u590D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" })
|
|
480
635
|
] });
|
|
481
636
|
}
|
|
482
|
-
function
|
|
637
|
+
function Message() {
|
|
483
638
|
const message = useAuiState((state) => state.message);
|
|
484
|
-
return
|
|
639
|
+
return /* @__PURE__ */ jsx5(MessageBody, { message });
|
|
485
640
|
}
|
|
486
641
|
function ChatThread() {
|
|
487
|
-
return /* @__PURE__ */
|
|
642
|
+
return /* @__PURE__ */ jsx5(ThreadPrimitive.Messages, { components: { Message } });
|
|
488
643
|
}
|
|
489
644
|
|
|
490
|
-
// packages/cli/src/
|
|
491
|
-
import {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
645
|
+
// packages/cli/src/composer.tsx
|
|
646
|
+
import { ComposerPrimitive, LoadingPrimitive, useAui, useAuiState as useAuiState2 } from "@assistant-ui/react-ink";
|
|
647
|
+
import { Box as Box6, Text as Text6, useFocusManager, useInput as useInput4 } from "ink";
|
|
648
|
+
import { useEffect as useEffect2, useRef as useRef4 } from "react";
|
|
649
|
+
|
|
650
|
+
// src/agent/appearance.ts
|
|
651
|
+
var composerPlaceholder = "\u95EE\u70B9\u4EC0\u4E48\uFF0C\u6216\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4";
|
|
652
|
+
var composerBrand = { mark: "<MT/>", model: "V3.14 Pro" };
|
|
653
|
+
|
|
654
|
+
// src/agent/commands.ts
|
|
655
|
+
var commands = [
|
|
656
|
+
{ id: "help", description: "\u67E5\u770B\u53EF\u7528\u547D\u4EE4" },
|
|
657
|
+
{ id: "theme", args: "[\u4E3B\u9898]", description: "\u5207\u6362\u4E3B\u9898\uFF0C\u4E0D\u5E26\u53C2\u6570\u65F6\u6253\u5F00\u9009\u62E9\u5668" },
|
|
658
|
+
{ id: "clear", description: "\u6E05\u7A7A\u5F53\u524D\u5BF9\u8BDD" },
|
|
659
|
+
{ id: "home", description: "\u6253\u5F00\u9996\u9875" },
|
|
660
|
+
{ id: "blog", description: "\u6D4F\u89C8\u5168\u90E8\u6587\u7AE0" },
|
|
661
|
+
{ id: "projects", description: "\u67E5\u770B\u6298\u817E\u7684\u9879\u76EE" },
|
|
662
|
+
{ id: "tags", description: "\u6309\u6807\u7B7E\u6D4F\u89C8\u6587\u7AE0" },
|
|
663
|
+
{ id: "search", args: "[\u5173\u952E\u8BCD]", description: "\u641C\u7D22\u6587\u7AE0" },
|
|
664
|
+
{ id: "about", description: "\u5173\u4E8E\u672C\u7AD9" },
|
|
665
|
+
{ id: "rss", description: "\u8BA2\u9605 RSS" }
|
|
666
|
+
];
|
|
667
|
+
var pageCommands = {
|
|
668
|
+
home: "/",
|
|
669
|
+
blog: "/blog/",
|
|
670
|
+
projects: "/projects/",
|
|
671
|
+
tags: "/tags/",
|
|
672
|
+
about: "/about/"
|
|
673
|
+
};
|
|
674
|
+
function parseCommand(input) {
|
|
675
|
+
const match = input.trim().match(/^\/(\S+)(?:\s+([\s\S]*))?$/);
|
|
676
|
+
if (!match)
|
|
677
|
+
return null;
|
|
678
|
+
return { name: match[1].toLowerCase(), args: (match[2] ?? "").trim() };
|
|
679
|
+
}
|
|
680
|
+
function getSearchPath(query) {
|
|
681
|
+
return query ? `/search/?q=${encodeURIComponent(query)}` : "/search/";
|
|
510
682
|
}
|
|
511
|
-
|
|
512
|
-
|
|
683
|
+
|
|
684
|
+
// packages/cli/src/command-menu.tsx
|
|
685
|
+
import { Box as Box5, Text as Text5, useInput as useInput3, useWindowSize } from "ink";
|
|
686
|
+
import { useImperativeHandle as useImperativeHandle2, useState as useState3 } from "react";
|
|
687
|
+
|
|
688
|
+
// packages/cli/src/commands.ts
|
|
689
|
+
var terminalCommands = [
|
|
690
|
+
...commands.map((command) => command.id === "tags" ? { ...command, args: "[\u6807\u7B7E]" } : command),
|
|
691
|
+
{ id: "read", args: "<\u7F16\u53F7\u3001slug \u6216\u8DEF\u5F84>", description: "\u9605\u8BFB\u6587\u7AE0\u5168\u6587" },
|
|
692
|
+
{ id: "open", args: "[\u7F16\u53F7\u6216 URL]", description: "\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u5F53\u524D\u9875\u9762\u6216\u94FE\u63A5" },
|
|
693
|
+
{ id: "refresh", description: "\u4ECE\u5B98\u7F51\u5237\u65B0\u5F53\u524D\u9875\u9762" },
|
|
694
|
+
{ id: "quit", description: "\u9000\u51FA" }
|
|
695
|
+
];
|
|
696
|
+
function getCommandSuggestions(text) {
|
|
697
|
+
const match = text.match(/^\/([^\s]*)$/);
|
|
698
|
+
if (!match) return null;
|
|
699
|
+
const query = match[1].toLowerCase();
|
|
700
|
+
return terminalCommands.filter((command) => command.id.startsWith(query) || command.description.includes(query));
|
|
701
|
+
}
|
|
702
|
+
function numberedItem(input, page) {
|
|
703
|
+
if (!/^\d+$/.test(input)) return void 0;
|
|
704
|
+
const item = page.items[Number(input) - 1];
|
|
705
|
+
if (!item) throw new Error("\u5F53\u524D\u5217\u8868\u6CA1\u6709\u8FD9\u4E2A\u7F16\u53F7\uFF0C\u8BF7\u4F7F\u7528\u5217\u8868\u4E2D\u663E\u793A\u7684\u7F16\u53F7\u3002");
|
|
706
|
+
return item;
|
|
707
|
+
}
|
|
708
|
+
function resolveReadPath(input, page) {
|
|
709
|
+
if (!input) throw new Error("\u8F93\u5165 /read <\u7F16\u53F7\u3001slug \u6216\u7AD9\u5185\u8DEF\u5F84> \u9605\u8BFB\u6587\u7AE0\u3002");
|
|
710
|
+
const item = numberedItem(input, page);
|
|
711
|
+
if (item) {
|
|
712
|
+
if (item.external || !item.path.startsWith("/posts/"))
|
|
713
|
+
throw new Error("\u8FD9\u4E2A\u7F16\u53F7\u4E0D\u662F\u6587\u7AE0\uFF0C\u8BF7\u4F7F\u7528 /blog \u6216 /search \u67E5\u627E\u6587\u7AE0\u3002");
|
|
714
|
+
return item.path;
|
|
715
|
+
}
|
|
716
|
+
return input.startsWith("/") ? input : `/posts/${input.replace(/\/$/, "")}/`;
|
|
717
|
+
}
|
|
718
|
+
function resolveOpenTarget(input, page) {
|
|
719
|
+
return numberedItem(input, page)?.path ?? (input || page.path);
|
|
720
|
+
}
|
|
721
|
+
function resolveTagPath(input, index) {
|
|
722
|
+
const tag = index.tags.find((tag2) => tag2.name === input);
|
|
723
|
+
if (!tag) throw new Error("\u627E\u4E0D\u5230\u8FD9\u4E2A\u6807\u7B7E\uFF0C\u8BF7\u4F7F\u7528 /tags \u67E5\u770B\u53EF\u7528\u6807\u7B7E\u3002");
|
|
724
|
+
return tag.path;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// packages/cli/src/command-menu.tsx
|
|
728
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
729
|
+
function CommandMenu({
|
|
730
|
+
text,
|
|
731
|
+
focused,
|
|
732
|
+
onSelect,
|
|
733
|
+
ref
|
|
734
|
+
}) {
|
|
513
735
|
const { colors } = useTerminalStyle();
|
|
514
|
-
const
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
736
|
+
const { rows } = useWindowSize();
|
|
737
|
+
const [selected, setSelected] = useState3(0);
|
|
738
|
+
const [dismissed, setDismissed] = useState3(false);
|
|
739
|
+
const suggestions = getCommandSuggestions(text);
|
|
740
|
+
const matches = suggestions ?? [];
|
|
741
|
+
const open = focused && suggestions !== null && !dismissed;
|
|
742
|
+
const visibleCount = Math.max(1, Math.min(8, rows - 10));
|
|
743
|
+
const start = Math.max(0, selected - visibleCount + 1);
|
|
744
|
+
useImperativeHandle2(ref, () => ({
|
|
745
|
+
submit: (input) => {
|
|
746
|
+
if (!open || input !== text || !matches.length) return false;
|
|
747
|
+
onSelect(matches[selected]);
|
|
748
|
+
return true;
|
|
524
749
|
},
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
750
|
+
dismiss: () => {
|
|
751
|
+
if (!open) return false;
|
|
752
|
+
setDismissed(true);
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
}));
|
|
756
|
+
useInput3((_input, key) => {
|
|
757
|
+
if (!matches.length) return;
|
|
758
|
+
const direction = Number(key.downArrow) - Number(key.upArrow);
|
|
759
|
+
if (direction) setSelected((index) => (index + direction + matches.length) % matches.length);
|
|
760
|
+
}, { isActive: open });
|
|
761
|
+
if (!open) return null;
|
|
762
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: colors.line, paddingX: 1, marginBottom: 1, children: [
|
|
763
|
+
matches.length === 0 && /* @__PURE__ */ jsx6(Text5, { color: colors.muted, children: "\u6CA1\u6709\u5339\u914D\u7684\u547D\u4EE4" }),
|
|
764
|
+
matches.slice(start, start + visibleCount).map((command, index) => {
|
|
765
|
+
const active = start + index === selected;
|
|
766
|
+
return /* @__PURE__ */ jsxs5(Box5, { height: 1, backgroundColor: active ? colors.band : void 0, children: [
|
|
767
|
+
/* @__PURE__ */ jsx6(Box5, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsxs5(Text5, { color: active ? colors.accent : colors.skill, bold: active, children: [
|
|
768
|
+
active ? "\u25B8" : " ",
|
|
769
|
+
" /",
|
|
770
|
+
command.id
|
|
771
|
+
] }) }),
|
|
772
|
+
/* @__PURE__ */ jsx6(Box5, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx6(Text5, { color: colors.muted, wrap: "truncate-end", children: command.description }) })
|
|
773
|
+
] }, command.id);
|
|
541
774
|
}),
|
|
542
|
-
/* @__PURE__ */
|
|
543
|
-
start + 1,
|
|
544
|
-
"\u2013",
|
|
545
|
-
Math.min(start + pageSize, page.items.length),
|
|
546
|
-
" / ",
|
|
547
|
-
page.items.length,
|
|
548
|
-
" \xB7 Tab",
|
|
549
|
-
" ",
|
|
550
|
-
isFocused ? "\u56DE\u5230\u8F93\u5165" : "\u9009\u62E9\u5217\u8868"
|
|
551
|
-
] })
|
|
775
|
+
/* @__PURE__ */ jsx6(Text5, { color: colors.muted, wrap: "truncate-end", children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u5173\u95ED" })
|
|
552
776
|
] });
|
|
553
777
|
}
|
|
554
|
-
|
|
778
|
+
|
|
779
|
+
// packages/cli/src/composer.tsx
|
|
780
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
781
|
+
function Composer({
|
|
555
782
|
runtime,
|
|
556
783
|
page,
|
|
557
784
|
revision,
|
|
558
|
-
output,
|
|
559
785
|
loading,
|
|
560
786
|
notice,
|
|
561
787
|
onCommand,
|
|
562
|
-
|
|
788
|
+
onCancel,
|
|
789
|
+
onSubmit
|
|
563
790
|
}) {
|
|
564
791
|
const { colors } = useTerminalStyle();
|
|
565
792
|
const aui = useAui();
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
793
|
+
const { activeId, focus, focusNext } = useFocusManager();
|
|
794
|
+
const running = useAuiState2((state) => state.thread.isRunning);
|
|
795
|
+
const text = useAuiState2((state) => state.composer.text);
|
|
796
|
+
const menu = useRef4(null);
|
|
797
|
+
const inputFocusId = useRef4(void 0);
|
|
798
|
+
const focused = activeId !== "content";
|
|
799
|
+
useEffect2(() => {
|
|
800
|
+
if (activeId && activeId !== "content") inputFocusId.current = activeId;
|
|
801
|
+
}, [activeId]);
|
|
802
|
+
useInput4((input, key) => {
|
|
803
|
+
if (key.escape) {
|
|
804
|
+
if (!menu.current?.dismiss()) onCancel();
|
|
805
|
+
if (inputFocusId.current) focus(inputFocusId.current);
|
|
806
|
+
}
|
|
807
|
+
if (input === "/" && activeId === "content") {
|
|
808
|
+
focusNext();
|
|
809
|
+
aui.composer.setText("/");
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginX: 2, flexShrink: 0, children: [
|
|
813
|
+
/* @__PURE__ */ jsx7(
|
|
814
|
+
CommandMenu,
|
|
815
|
+
{
|
|
816
|
+
ref: menu,
|
|
817
|
+
text,
|
|
818
|
+
focused,
|
|
819
|
+
onSelect: (command) => {
|
|
820
|
+
if (command.id === "search" || command.id === "read") {
|
|
821
|
+
aui.composer.setText(`/${command.id} `);
|
|
822
|
+
} else {
|
|
823
|
+
aui.composer.setText("");
|
|
824
|
+
void onCommand(command.id, "");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
text
|
|
829
|
+
),
|
|
830
|
+
(loading || running || notice) && /* @__PURE__ */ jsxs6(Box6, { paddingLeft: 2, height: 1, overflow: "hidden", children: [
|
|
831
|
+
(loading || running) && /* @__PURE__ */ jsx7(LoadingPrimitive.Spinner, {}),
|
|
832
|
+
/* @__PURE__ */ jsx7(Text6, { color: notice ? colors.warn : colors.muted, wrap: "truncate-end", children: notice ? cleanText(notice) : ` ${loading ? "\u6B63\u5728\u8BFB\u53D6\u5B98\u7F51\u2026" : "\u56DE\u590D\u4E2D\u2026"} \xB7 Esc \u505C\u6B62` })
|
|
578
833
|
] }),
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
/* @__PURE__ */
|
|
582
|
-
/* @__PURE__ */ jsx3(
|
|
834
|
+
/* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: focused ? colors["line-strong"] : colors.line, paddingX: 1, children: [
|
|
835
|
+
/* @__PURE__ */ jsx7(Text6, { color: colors.prompt, children: "\u276F " }),
|
|
836
|
+
/* @__PURE__ */ jsx7(
|
|
583
837
|
ComposerPrimitive.Input,
|
|
584
838
|
{
|
|
585
839
|
autoFocus: true,
|
|
586
840
|
submitOnEnter: true,
|
|
587
841
|
flexGrow: 1,
|
|
588
|
-
placeholder:
|
|
589
|
-
onSubmit: (
|
|
590
|
-
const input =
|
|
842
|
+
placeholder: composerPlaceholder,
|
|
843
|
+
onSubmit: (text2) => {
|
|
844
|
+
const input = text2.trim();
|
|
591
845
|
if (!input) return;
|
|
592
|
-
|
|
593
|
-
if (
|
|
846
|
+
if (menu.current?.submit(text2)) return;
|
|
847
|
+
if (input.startsWith("/")) {
|
|
848
|
+
const command = parseCommand(input);
|
|
594
849
|
aui.composer.setText("");
|
|
595
|
-
void onCommand(command
|
|
596
|
-
} else if (!
|
|
850
|
+
void onCommand(command?.name ?? "help", command?.args ?? "");
|
|
851
|
+
} else if (!running) {
|
|
852
|
+
onSubmit();
|
|
853
|
+
runtime.thread.composer.send();
|
|
854
|
+
}
|
|
597
855
|
}
|
|
598
856
|
},
|
|
599
|
-
page.path
|
|
600
|
-
)
|
|
857
|
+
`${page.path}:${revision}`
|
|
858
|
+
),
|
|
859
|
+
/* @__PURE__ */ jsx7(Box6, { position: "absolute", bottom: -1, right: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: colors.muted, backgroundColor: colors.bg, children: [
|
|
860
|
+
" ",
|
|
861
|
+
composerBrand.mark,
|
|
862
|
+
" \xB7 ",
|
|
863
|
+
composerBrand.model,
|
|
864
|
+
" "
|
|
865
|
+
] }) })
|
|
601
866
|
] }),
|
|
602
|
-
/* @__PURE__ */
|
|
603
|
-
|
|
604
|
-
"
|
|
867
|
+
/* @__PURE__ */ jsxs6(Text6, { color: colors.muted, wrap: "truncate-end", children: [
|
|
868
|
+
/* @__PURE__ */ jsx7(Text6, { color: colors["fg-soft"], bold: true, children: "Tab" }),
|
|
869
|
+
":\u5207\u6362 \u2502 ",
|
|
870
|
+
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "PgUp/PgDn" }),
|
|
871
|
+
":\u6EDA\u52A8 \u2502 ",
|
|
872
|
+
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "/help" }),
|
|
873
|
+
" \u2502 Ctrl+C:\u9000\u51FA"
|
|
605
874
|
] })
|
|
606
875
|
] });
|
|
607
876
|
}
|
|
608
877
|
|
|
878
|
+
// packages/cli/src/page-content.tsx
|
|
879
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
880
|
+
import { memo } from "react";
|
|
881
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
882
|
+
var PageContent = memo(function PageContent2({
|
|
883
|
+
page,
|
|
884
|
+
prompt,
|
|
885
|
+
onSelect,
|
|
886
|
+
scroll
|
|
887
|
+
}) {
|
|
888
|
+
const { colors } = useTerminalStyle();
|
|
889
|
+
if (page.sections) return /* @__PURE__ */ jsx8(HomeSections, { page, onSelect, scroll });
|
|
890
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
891
|
+
page.path !== "/" && /* @__PURE__ */ jsx8(Prompt, { text: prompt, command: true }),
|
|
892
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 2, paddingRight: 1, paddingTop: 1, children: [
|
|
893
|
+
page.path !== "/" && /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
894
|
+
/* @__PURE__ */ jsx8(Text7, { color: colors.skill, children: "\u25C6 " }),
|
|
895
|
+
/* @__PURE__ */ jsx8(Text7, { color: colors.fg, bold: true, children: page.verb }),
|
|
896
|
+
/* @__PURE__ */ jsxs7(Text7, { color: colors.path, children: [
|
|
897
|
+
" ",
|
|
898
|
+
cleanText(page.title)
|
|
899
|
+
] })
|
|
900
|
+
] }),
|
|
901
|
+
page.markdown && /* @__PURE__ */ jsx8(Markdown, { text: page.markdown }),
|
|
902
|
+
page.items.length > 0 && /* @__PURE__ */ jsx8(PageList, { items: page.items, onSelect, scroll })
|
|
903
|
+
] })
|
|
904
|
+
] });
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// packages/cli/src/terminal-layout.tsx
|
|
908
|
+
import { Box as Box8, Text as Text8, useWindowSize as useWindowSize2 } from "ink";
|
|
909
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
910
|
+
function TerminalLayout({
|
|
911
|
+
path,
|
|
912
|
+
nav = [],
|
|
913
|
+
children
|
|
914
|
+
}) {
|
|
915
|
+
const { columns, rows } = useWindowSize2();
|
|
916
|
+
const { colors } = useTerminalStyle();
|
|
917
|
+
const directory = `~/miantiao.me${path === "/" ? "" : path.replace(/\/$/, "")}`;
|
|
918
|
+
return /* @__PURE__ */ jsxs8(
|
|
919
|
+
Box8,
|
|
920
|
+
{
|
|
921
|
+
width: columns,
|
|
922
|
+
height: rows,
|
|
923
|
+
flexDirection: "column",
|
|
924
|
+
backgroundColor: colors.bg,
|
|
925
|
+
overflow: "hidden",
|
|
926
|
+
children: [
|
|
927
|
+
/* @__PURE__ */ jsxs8(Box8, { paddingX: 2, flexShrink: 0, children: [
|
|
928
|
+
/* @__PURE__ */ jsx9(Box8, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx9(Text8, { color: colors.muted, wrap: "truncate-middle", children: cleanText(directory) }) }),
|
|
929
|
+
columns >= 70 && nav.map((link) => /* @__PURE__ */ jsx9(Box8, { marginLeft: 2, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text8, { color: path.startsWith(link.href) ? colors.fg : colors.muted, children: cleanText(link.title) }) }, link.href))
|
|
930
|
+
] }),
|
|
931
|
+
children
|
|
932
|
+
]
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// packages/cli/src/console.tsx
|
|
938
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
939
|
+
function Console({
|
|
940
|
+
runtime,
|
|
941
|
+
page,
|
|
942
|
+
prompt,
|
|
943
|
+
revision,
|
|
944
|
+
site,
|
|
945
|
+
loading,
|
|
946
|
+
notice,
|
|
947
|
+
onCommand,
|
|
948
|
+
onCancel,
|
|
949
|
+
onSelect
|
|
950
|
+
}) {
|
|
951
|
+
const scroll = useRef5(null);
|
|
952
|
+
return /* @__PURE__ */ jsx10(TerminalLayout, { path: page.path, nav: site.nav, children: /* @__PURE__ */ jsxs9(ThreadPrimitive2.Root, { flexGrow: 1, flexShrink: 1, minHeight: 0, children: [
|
|
953
|
+
/* @__PURE__ */ jsxs9(ScrollArea, { ref: scroll, selection: page.items.length > 0, children: [
|
|
954
|
+
/* @__PURE__ */ jsx10(PageContent, { page, prompt, onSelect, scroll }),
|
|
955
|
+
/* @__PURE__ */ jsx10(ChatThread, {})
|
|
956
|
+
] }, `${page.path}:${revision}`),
|
|
957
|
+
/* @__PURE__ */ jsx10(
|
|
958
|
+
Composer,
|
|
959
|
+
{
|
|
960
|
+
runtime,
|
|
961
|
+
page,
|
|
962
|
+
revision,
|
|
963
|
+
loading,
|
|
964
|
+
notice,
|
|
965
|
+
onCommand,
|
|
966
|
+
onCancel,
|
|
967
|
+
onSubmit: () => scroll.current?.end()
|
|
968
|
+
}
|
|
969
|
+
)
|
|
970
|
+
] }) });
|
|
971
|
+
}
|
|
972
|
+
|
|
609
973
|
// packages/cli/src/pages.ts
|
|
610
|
-
var terminalCommands = [
|
|
611
|
-
...commands.map((command) => command.id === "tags" ? { ...command, args: "[\u6807\u7B7E]" } : command),
|
|
612
|
-
{ id: "read", args: "<\u7F16\u53F7\u3001slug \u6216\u8DEF\u5F84>", description: "\u9605\u8BFB\u6587\u7AE0\u5168\u6587" },
|
|
613
|
-
{ id: "open", args: "[\u7F16\u53F7\u6216 URL]", description: "\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u5F53\u524D\u9875\u9762\u6216\u94FE\u63A5" },
|
|
614
|
-
{ id: "refresh", description: "\u4ECE\u5B98\u7F51\u5237\u65B0\u5F53\u524D\u9875\u9762" },
|
|
615
|
-
{ id: "quit", description: "\u9000\u51FA" }
|
|
616
|
-
];
|
|
617
974
|
function helpPage() {
|
|
618
975
|
return {
|
|
619
976
|
path: "/help/",
|
|
@@ -622,7 +979,7 @@ function helpPage() {
|
|
|
622
979
|
items: [],
|
|
623
980
|
markdown: terminalCommands.map(
|
|
624
981
|
(command) => `- \`/${command.id}${command.args ? ` ${command.args}` : ""}\` \u2014 ${command.description}`
|
|
625
|
-
).join("\n") + "\n\
|
|
982
|
+
).join("\n") + "\n\n/ \u6253\u5F00\u547D\u4EE4\u9762\u677F\uFF1B\u2191\u2193 \u9009\u62E9\u3001Enter \u786E\u8BA4\u3001Esc \u5173\u95ED\u3002PgUp/PgDn \u6EDA\u52A8\u6B63\u6587\uFF1BTab \u5207\u6362\u8F93\u5165\u548C\u5185\u5BB9\uFF1BEsc \u505C\u6B62\uFF1BCtrl+C \u9000\u51FA\u3002"
|
|
626
983
|
};
|
|
627
984
|
}
|
|
628
985
|
function themePage() {
|
|
@@ -651,13 +1008,23 @@ async function loadPage(client, path, signal) {
|
|
|
651
1008
|
page.path = result.document.path + url.search;
|
|
652
1009
|
page.title = result.document.title;
|
|
653
1010
|
page.markdown = result.document.markdown;
|
|
1011
|
+
page.sections = result.document.sections;
|
|
654
1012
|
page.verb = "Read";
|
|
655
|
-
if (page.path === index.pages.home.path)
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
1013
|
+
if (page.path === index.pages.home.path) {
|
|
1014
|
+
const welcome = page.sections?.find((section) => section.kind === "welcome");
|
|
1015
|
+
const menu = welcome?.menu ?? index.site.nav.map((link) => ({ ...link, command: void 0 }));
|
|
1016
|
+
const siteOrigins = new Set([client.site, index.site.url].map((site) => new URL(site).origin));
|
|
1017
|
+
page.items = menu.map((link) => {
|
|
1018
|
+
const destination = new URL(link.href, client.site);
|
|
1019
|
+
const external = !siteOrigins.has(destination.origin);
|
|
1020
|
+
return {
|
|
1021
|
+
title: link.title,
|
|
1022
|
+
description: link.command,
|
|
1023
|
+
path: external ? destination.href : destination.pathname + destination.search,
|
|
1024
|
+
external
|
|
1025
|
+
};
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
661
1028
|
} else if (pathname === "/blog/") {
|
|
662
1029
|
page.title = "\u5168\u90E8\u6587\u7AE0";
|
|
663
1030
|
page.items = index.posts.map((post) => ({
|
|
@@ -706,12 +1073,12 @@ async function loadPage(client, path, signal) {
|
|
|
706
1073
|
}
|
|
707
1074
|
|
|
708
1075
|
// packages/cli/src/session.ts
|
|
709
|
-
import { useApp, useInput as
|
|
710
|
-
import {
|
|
1076
|
+
import { useApp, useInput as useInput5 } from "ink";
|
|
1077
|
+
import { useState as useState5 } from "react";
|
|
711
1078
|
|
|
712
1079
|
// packages/cli/src/agent.ts
|
|
713
1080
|
import { useChatRuntime } from "@assistant-ui/ai-sdk";
|
|
714
|
-
import { useEffect, useMemo as
|
|
1081
|
+
import { useEffect as useEffect3, useMemo as useMemo3 } from "react";
|
|
715
1082
|
|
|
716
1083
|
// src/agent/transport.ts
|
|
717
1084
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
@@ -857,7 +1224,7 @@ function createAgentTransport(handlers, options) {
|
|
|
857
1224
|
|
|
858
1225
|
// packages/cli/src/agent.ts
|
|
859
1226
|
function useTerminalAgent({ site, index, page, theme, setTheme, navigate }) {
|
|
860
|
-
const transport =
|
|
1227
|
+
const transport = useMemo3(
|
|
861
1228
|
() => createAgentTransport(
|
|
862
1229
|
{
|
|
863
1230
|
getContext: () => {
|
|
@@ -900,7 +1267,7 @@ function useTerminalAgent({ site, index, page, theme, setTheme, navigate }) {
|
|
|
900
1267
|
]
|
|
901
1268
|
);
|
|
902
1269
|
const runtime = useChatRuntime({ transport });
|
|
903
|
-
|
|
1270
|
+
useEffect3(() => () => runtime.thread.cancelRun(), [runtime]);
|
|
904
1271
|
return runtime;
|
|
905
1272
|
}
|
|
906
1273
|
|
|
@@ -921,42 +1288,17 @@ async function openBrowser(input, site) {
|
|
|
921
1288
|
});
|
|
922
1289
|
}
|
|
923
1290
|
|
|
924
|
-
// packages/cli/src/commands.ts
|
|
925
|
-
function numberedItem(input, page) {
|
|
926
|
-
if (!/^\d+$/.test(input)) return void 0;
|
|
927
|
-
const item = page.items[Number(input) - 1];
|
|
928
|
-
if (!item) throw new Error("\u5F53\u524D\u5217\u8868\u6CA1\u6709\u8FD9\u4E2A\u7F16\u53F7\uFF0C\u8BF7\u4F7F\u7528\u5217\u8868\u4E2D\u663E\u793A\u7684\u7F16\u53F7\u3002");
|
|
929
|
-
return item;
|
|
930
|
-
}
|
|
931
|
-
function resolveReadPath(input, page) {
|
|
932
|
-
if (!input) throw new Error("\u8F93\u5165 /read <\u7F16\u53F7\u3001slug \u6216\u7AD9\u5185\u8DEF\u5F84> \u9605\u8BFB\u6587\u7AE0\u3002");
|
|
933
|
-
const item = numberedItem(input, page);
|
|
934
|
-
if (item) {
|
|
935
|
-
if (item.external || !item.path.startsWith("/posts/"))
|
|
936
|
-
throw new Error("\u8FD9\u4E2A\u7F16\u53F7\u4E0D\u662F\u6587\u7AE0\uFF0C\u8BF7\u4F7F\u7528 /blog \u6216 /search \u67E5\u627E\u6587\u7AE0\u3002");
|
|
937
|
-
return item.path;
|
|
938
|
-
}
|
|
939
|
-
return input.startsWith("/") ? input : `/posts/${input.replace(/\/$/, "")}/`;
|
|
940
|
-
}
|
|
941
|
-
function resolveOpenTarget(input, page) {
|
|
942
|
-
return numberedItem(input, page)?.path ?? (input || page.path);
|
|
943
|
-
}
|
|
944
|
-
function resolveTagPath(input, index) {
|
|
945
|
-
const tag = index.tags.find((tag2) => tag2.name === input);
|
|
946
|
-
if (!tag) throw new Error("\u627E\u4E0D\u5230\u8FD9\u4E2A\u6807\u7B7E\uFF0C\u8BF7\u4F7F\u7528 /tags \u67E5\u770B\u53EF\u7528\u6807\u7B7E\u3002");
|
|
947
|
-
return tag.path;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
1291
|
// packages/cli/src/navigation.ts
|
|
951
|
-
import { useCallback, useEffect as
|
|
952
|
-
function useNavigation(client, initial
|
|
953
|
-
const [state, setState] =
|
|
1292
|
+
import { useCallback, useEffect as useEffect4, useRef as useRef6, useState as useState4 } from "react";
|
|
1293
|
+
function useNavigation(client, initial) {
|
|
1294
|
+
const [state, setState] = useState4({
|
|
954
1295
|
...initial,
|
|
955
1296
|
status: "idle",
|
|
956
|
-
notice: client.warning
|
|
1297
|
+
notice: client.warning,
|
|
1298
|
+
prompt: "/home"
|
|
957
1299
|
});
|
|
958
|
-
const active =
|
|
959
|
-
|
|
1300
|
+
const active = useRef6(void 0);
|
|
1301
|
+
useEffect4(
|
|
960
1302
|
() => () => {
|
|
961
1303
|
active.current?.abort();
|
|
962
1304
|
active.current = void 0;
|
|
@@ -974,10 +1316,9 @@ function useNavigation(client, initial, onPage) {
|
|
|
974
1316
|
const show = useCallback(
|
|
975
1317
|
(page, prompt) => {
|
|
976
1318
|
cancel();
|
|
977
|
-
setState((current) => ({ ...current, page, notice: "" }));
|
|
978
|
-
onPage(page, prompt);
|
|
1319
|
+
setState((current) => ({ ...current, page, prompt, notice: "" }));
|
|
979
1320
|
},
|
|
980
|
-
[cancel
|
|
1321
|
+
[cancel]
|
|
981
1322
|
);
|
|
982
1323
|
const navigate = useCallback(
|
|
983
1324
|
async (path, prompt = path) => {
|
|
@@ -1001,11 +1342,10 @@ function useNavigation(client, initial, onPage) {
|
|
|
1001
1342
|
}
|
|
1002
1343
|
if (active.current !== request) return false;
|
|
1003
1344
|
active.current = void 0;
|
|
1004
|
-
setState({ ...next, status: "idle", notice: client.warning });
|
|
1005
|
-
onPage(next.page, prompt);
|
|
1345
|
+
setState({ ...next, status: "idle", notice: client.warning, prompt });
|
|
1006
1346
|
return true;
|
|
1007
1347
|
},
|
|
1008
|
-
[client
|
|
1348
|
+
[client]
|
|
1009
1349
|
);
|
|
1010
1350
|
return { ...state, navigate, show, cancel, notify };
|
|
1011
1351
|
}
|
|
@@ -1013,48 +1353,23 @@ function useNavigation(client, initial, onPage) {
|
|
|
1013
1353
|
// packages/cli/src/session.ts
|
|
1014
1354
|
function useTerminalSession(client, initial) {
|
|
1015
1355
|
const { exit } = useApp();
|
|
1016
|
-
const [theme, setTheme] =
|
|
1017
|
-
const
|
|
1018
|
-
{ id: "home", page: initial.page, prompt: "/home" }
|
|
1019
|
-
]);
|
|
1020
|
-
const appendPage = useCallback2((page2, prompt) => {
|
|
1021
|
-
const entry = { id: crypto.randomUUID(), page: page2, prompt };
|
|
1022
|
-
setOutput((items) => [...items, entry]);
|
|
1023
|
-
}, []);
|
|
1024
|
-
const { index, page, status, notice, navigate, show, cancel, notify } = useNavigation(
|
|
1025
|
-
client,
|
|
1026
|
-
initial,
|
|
1027
|
-
appendPage
|
|
1028
|
-
);
|
|
1356
|
+
const [theme, setTheme] = useState5("auto");
|
|
1357
|
+
const { index, page, prompt, status, notice, navigate, show, cancel, notify } = useNavigation(client, initial);
|
|
1029
1358
|
const runtime = useTerminalAgent({ site: client.site, index, page, theme, setTheme, navigate });
|
|
1030
|
-
useEffect3(() => {
|
|
1031
|
-
const printed = /* @__PURE__ */ new Set();
|
|
1032
|
-
const publish = () => {
|
|
1033
|
-
const completed = runtime.thread.getState().messages.filter(
|
|
1034
|
-
(message) => !printed.has(message.id) && (message.role !== "assistant" || message.status.type !== "running")
|
|
1035
|
-
);
|
|
1036
|
-
if (!completed.length) return;
|
|
1037
|
-
const entries = completed.map((message) => ({ id: message.id, message }));
|
|
1038
|
-
completed.forEach((message) => printed.add(message.id));
|
|
1039
|
-
setOutput((items) => [...items, ...entries]);
|
|
1040
|
-
};
|
|
1041
|
-
publish();
|
|
1042
|
-
return runtime.thread.subscribe(publish);
|
|
1043
|
-
}, [runtime]);
|
|
1044
1359
|
const stop = () => {
|
|
1045
1360
|
cancel();
|
|
1046
1361
|
runtime.thread.cancelRun();
|
|
1047
1362
|
};
|
|
1048
|
-
|
|
1049
|
-
if (key.escape) {
|
|
1050
|
-
stop();
|
|
1051
|
-
notify("\u5DF2\u505C\u6B62\u3002");
|
|
1052
|
-
}
|
|
1363
|
+
useInput5((input, key) => {
|
|
1053
1364
|
if (key.ctrl && input === "c") {
|
|
1054
1365
|
stop();
|
|
1055
1366
|
exit();
|
|
1056
1367
|
}
|
|
1057
1368
|
});
|
|
1369
|
+
const onCancel = () => {
|
|
1370
|
+
stop();
|
|
1371
|
+
notify("\u5DF2\u505C\u6B62\u3002");
|
|
1372
|
+
};
|
|
1058
1373
|
const onCommand = async (name, args) => {
|
|
1059
1374
|
try {
|
|
1060
1375
|
if (pageCommands[name] && !(name === "tags" && args)) {
|
|
@@ -1117,44 +1432,35 @@ function useTerminalSession(client, initial) {
|
|
|
1117
1432
|
runtime,
|
|
1118
1433
|
page,
|
|
1119
1434
|
revision: index.revision,
|
|
1120
|
-
|
|
1435
|
+
prompt,
|
|
1436
|
+
site: index.site,
|
|
1121
1437
|
loading: status === "loading",
|
|
1122
1438
|
notice,
|
|
1123
1439
|
onCommand,
|
|
1440
|
+
onCancel,
|
|
1124
1441
|
onSelect,
|
|
1125
1442
|
theme
|
|
1126
1443
|
};
|
|
1127
1444
|
}
|
|
1128
1445
|
|
|
1129
1446
|
// packages/cli/src/app.tsx
|
|
1130
|
-
import { jsx as
|
|
1447
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1131
1448
|
function Session({
|
|
1132
1449
|
client,
|
|
1133
1450
|
initial,
|
|
1134
1451
|
width
|
|
1135
1452
|
}) {
|
|
1136
1453
|
const { theme, ...session } = useTerminalSession(client, initial);
|
|
1137
|
-
return /* @__PURE__ */
|
|
1454
|
+
return /* @__PURE__ */ jsx11(TerminalTheme, { preference: theme, width, children: /* @__PURE__ */ jsx11(AssistantRuntimeProvider, { runtime: session.runtime, children: /* @__PURE__ */ jsx11(Console, { ...session }) }) });
|
|
1138
1455
|
}
|
|
1139
1456
|
function App({ site }) {
|
|
1140
1457
|
const { exit } = useApp2();
|
|
1141
|
-
const {
|
|
1142
|
-
const [client] =
|
|
1143
|
-
const [state, setState] =
|
|
1144
|
-
const [attempt, setAttempt] =
|
|
1145
|
-
const width =
|
|
1146
|
-
|
|
1147
|
-
(notify) => {
|
|
1148
|
-
stdout.on("resize", notify);
|
|
1149
|
-
return () => {
|
|
1150
|
-
stdout.off("resize", notify);
|
|
1151
|
-
};
|
|
1152
|
-
},
|
|
1153
|
-
[stdout]
|
|
1154
|
-
),
|
|
1155
|
-
() => "columns" in stdout && typeof stdout.columns === "number" ? stdout.columns : 80
|
|
1156
|
-
);
|
|
1157
|
-
useEffect4(() => {
|
|
1458
|
+
const { columns } = useWindowSize3();
|
|
1459
|
+
const [client] = useState6(() => new TerminalClient(site));
|
|
1460
|
+
const [state, setState] = useState6({ status: "loading" });
|
|
1461
|
+
const [attempt, setAttempt] = useState6(0);
|
|
1462
|
+
const width = Math.max(10, columns - 5);
|
|
1463
|
+
useEffect5(() => {
|
|
1158
1464
|
const request = new AbortController();
|
|
1159
1465
|
loadPage(client, "/", request.signal).then((content) => {
|
|
1160
1466
|
if (!request.signal.aborted) setState({ status: "ready", content });
|
|
@@ -1167,7 +1473,7 @@ function App({ site }) {
|
|
|
1167
1473
|
});
|
|
1168
1474
|
return () => request.abort();
|
|
1169
1475
|
}, [client, attempt]);
|
|
1170
|
-
|
|
1476
|
+
useInput6(
|
|
1171
1477
|
(input, key) => {
|
|
1172
1478
|
if (key.ctrl && input === "c" || input === "q") exit();
|
|
1173
1479
|
else if (state.status === "error" && (key.return || input === "r" || input === "/refresh")) {
|
|
@@ -1178,17 +1484,21 @@ function App({ site }) {
|
|
|
1178
1484
|
{ isActive: state.status !== "ready" }
|
|
1179
1485
|
);
|
|
1180
1486
|
if (state.status === "ready")
|
|
1181
|
-
return /* @__PURE__ */
|
|
1182
|
-
return /* @__PURE__ */
|
|
1183
|
-
/* @__PURE__ */
|
|
1487
|
+
return /* @__PURE__ */ jsx11(Session, { client, initial: state.content, width });
|
|
1488
|
+
return /* @__PURE__ */ jsx11(TerminalTheme, { preference: "auto", width, children: /* @__PURE__ */ jsx11(TerminalLayout, { path: "/", children: /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", flexGrow: 1, padding: 2, children: [
|
|
1489
|
+
/* @__PURE__ */ jsxs10(Text9, { children: [
|
|
1184
1490
|
"\u25C6 ",
|
|
1185
1491
|
state.status === "error" ? state.message : "\u6B63\u5728\u8BFB\u53D6\u5B98\u7F51\u2026"
|
|
1186
1492
|
] }),
|
|
1187
|
-
/* @__PURE__ */
|
|
1188
|
-
] });
|
|
1493
|
+
/* @__PURE__ */ jsx11(Text9, { children: state.status === "error" ? "Enter \u91CD\u8BD5 \xB7 q \u9000\u51FA" : "Ctrl+C \u9000\u51FA" })
|
|
1494
|
+
] }) }) });
|
|
1189
1495
|
}
|
|
1190
1496
|
async function run(site) {
|
|
1191
|
-
const app = render(/* @__PURE__ */
|
|
1497
|
+
const app = render(/* @__PURE__ */ jsx11(App, { site }), {
|
|
1498
|
+
exitOnCtrlC: false,
|
|
1499
|
+
alternateScreen: true,
|
|
1500
|
+
incrementalRendering: true
|
|
1501
|
+
});
|
|
1192
1502
|
await app.waitUntilExit();
|
|
1193
1503
|
}
|
|
1194
1504
|
export {
|
package/dist/index.js
CHANGED
|
@@ -16,14 +16,14 @@ try {
|
|
|
16
16
|
"miantiao.me \u2014 \u7EC8\u7AEF\u91CC\u7684\u9762\u6761\n\n\u7528\u6CD5\uFF1Anpx miantiao.me [--site <URL>]\n\n -h, --help \u67E5\u770B\u5E2E\u52A9\n -v, --version \u67E5\u770B\u7248\u672C\n --site <URL> \u5B98\u7F51\u5730\u5740\uFF0C\u4E5F\u53EF\u6307\u5411\u672C\u5730\u9884\u89C8\n\n\u8FDB\u5165\u540E\u8F93\u5165 /help \u67E5\u770B\u547D\u4EE4\uFF0CCtrl+C \u9000\u51FA\u3002\n\u9700\u8981 Node.js 22.14 \u6216\u66F4\u65B0\u7248\u672C\uFF0C\u4EE5\u53CA\u53EF\u4EA4\u4E92\u7684\u7EC8\u7AEF\u3002"
|
|
17
17
|
);
|
|
18
18
|
} else if (values.version) {
|
|
19
|
-
console.log("0.1.
|
|
19
|
+
console.log("0.1.2");
|
|
20
20
|
} else {
|
|
21
21
|
const site = new URL(values.site);
|
|
22
22
|
if (!["https:", "http:"].includes(site.protocol) || site.username || site.password)
|
|
23
23
|
throw new Error("--site \u5FC5\u987B\u4E3A\u4E0D\u542B\u51ED\u8BC1\u7684 HTTP \u6216 HTTPS \u5730\u5740\u3002");
|
|
24
24
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
25
25
|
throw new Error("\u8BF7\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u8FD0\u884C npx miantiao.me\uFF1B\u4F7F\u7528 --help \u67E5\u770B\u5E2E\u52A9\u3002");
|
|
26
|
-
const { run } = await import("./app-
|
|
26
|
+
const { run } = await import("./app-IW4THTCR.js");
|
|
27
27
|
await run(site.origin);
|
|
28
28
|
}
|
|
29
29
|
} catch (error) {
|