@monkey-mini-app/dsh-mini-app 0.1.0

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.
Files changed (112) hide show
  1. package/cordis.patch.yml +6 -0
  2. package/lib/client.d.cts +35 -0
  3. package/lib/client.js +5462 -0
  4. package/lib/index.d.ts +140 -0
  5. package/lib/index.js +1094 -0
  6. package/package.json +90 -0
  7. package/skills/monkey-mini-app/SKILL.md +209 -0
  8. package/skills/monkey-mini-app/references/catalog.md +99 -0
  9. package/skills/monkey-mini-app/references/contracts/activity-feed.md +24 -0
  10. package/skills/monkey-mini-app/references/contracts/app-shell.md +28 -0
  11. package/skills/monkey-mini-app/references/contracts/attachment-gallery.md +22 -0
  12. package/skills/monkey-mini-app/references/contracts/autocomplete.md +16 -0
  13. package/skills/monkey-mini-app/references/contracts/cascader.md +25 -0
  14. package/skills/monkey-mini-app/references/contracts/code-block.md +15 -0
  15. package/skills/monkey-mini-app/references/contracts/code-editor.md +17 -0
  16. package/skills/monkey-mini-app/references/contracts/color-picker.md +14 -0
  17. package/skills/monkey-mini-app/references/contracts/comment-thread.md +24 -0
  18. package/skills/monkey-mini-app/references/contracts/confirm-dialog.md +19 -0
  19. package/skills/monkey-mini-app/references/contracts/copyable.md +13 -0
  20. package/skills/monkey-mini-app/references/contracts/currency-input.md +15 -0
  21. package/skills/monkey-mini-app/references/contracts/data-grid.md +87 -0
  22. package/skills/monkey-mini-app/references/contracts/date-picker.md +17 -0
  23. package/skills/monkey-mini-app/references/contracts/date-range-picker.md +26 -0
  24. package/skills/monkey-mini-app/references/contracts/date-time-picker.md +18 -0
  25. package/skills/monkey-mini-app/references/contracts/date-time-range-picker.md +24 -0
  26. package/skills/monkey-mini-app/references/contracts/description-list.md +13 -0
  27. package/skills/monkey-mini-app/references/contracts/detail-panel.md +27 -0
  28. package/skills/monkey-mini-app/references/contracts/diff-viewer.md +16 -0
  29. package/skills/monkey-mini-app/references/contracts/donut-chart.md +31 -0
  30. package/skills/monkey-mini-app/references/contracts/duration-input.md +14 -0
  31. package/skills/monkey-mini-app/references/contracts/env-badge.md +13 -0
  32. package/skills/monkey-mini-app/references/contracts/event-calendar.md +50 -0
  33. package/skills/monkey-mini-app/references/contracts/file-dropzone.md +14 -0
  34. package/skills/monkey-mini-app/references/contracts/file-tree.md +23 -0
  35. package/skills/monkey-mini-app/references/contracts/filter-bar.md +26 -0
  36. package/skills/monkey-mini-app/references/contracts/gantt.md +24 -0
  37. package/skills/monkey-mini-app/references/contracts/gauge.md +14 -0
  38. package/skills/monkey-mini-app/references/contracts/jira-wiki.md +53 -0
  39. package/skills/monkey-mini-app/references/contracts/jql-input.md +37 -0
  40. package/skills/monkey-mini-app/references/contracts/json-viewer.md +13 -0
  41. package/skills/monkey-mini-app/references/contracts/kanban-issue-panel.md +45 -0
  42. package/skills/monkey-mini-app/references/contracts/kanban.md +64 -0
  43. package/skills/monkey-mini-app/references/contracts/log-viewer.md +13 -0
  44. package/skills/monkey-mini-app/references/contracts/markdown-editor.md +24 -0
  45. package/skills/monkey-mini-app/references/contracts/markdown.md +14 -0
  46. package/skills/monkey-mini-app/references/contracts/mini-calendar.md +15 -0
  47. package/skills/monkey-mini-app/references/contracts/notification-center.md +23 -0
  48. package/skills/monkey-mini-app/references/contracts/number-field.md +17 -0
  49. package/skills/monkey-mini-app/references/contracts/page-header.md +25 -0
  50. package/skills/monkey-mini-app/references/contracts/password-field.md +14 -0
  51. package/skills/monkey-mini-app/references/contracts/phone-input.md +14 -0
  52. package/skills/monkey-mini-app/references/contracts/progress-ring.md +13 -0
  53. package/skills/monkey-mini-app/references/contracts/radar-chart.md +23 -0
  54. package/skills/monkey-mini-app/references/contracts/rating.md +15 -0
  55. package/skills/monkey-mini-app/references/contracts/relative-date-picker.md +24 -0
  56. package/skills/monkey-mini-app/references/contracts/request-inspector.md +16 -0
  57. package/skills/monkey-mini-app/references/contracts/rich-text-editor.md +15 -0
  58. package/skills/monkey-mini-app/references/contracts/run-timeline.md +24 -0
  59. package/skills/monkey-mini-app/references/contracts/scrollspy.md +13 -0
  60. package/skills/monkey-mini-app/references/contracts/search-input.md +17 -0
  61. package/skills/monkey-mini-app/references/contracts/severity-chip.md +13 -0
  62. package/skills/monkey-mini-app/references/contracts/slider-range.md +16 -0
  63. package/skills/monkey-mini-app/references/contracts/sortable-list.md +23 -0
  64. package/skills/monkey-mini-app/references/contracts/sparkline.md +13 -0
  65. package/skills/monkey-mini-app/references/contracts/stacked-bar-chart.md +23 -0
  66. package/skills/monkey-mini-app/references/contracts/stat-card.md +27 -0
  67. package/skills/monkey-mini-app/references/contracts/status-badge.md +24 -0
  68. package/skills/monkey-mini-app/references/contracts/stepper.md +26 -0
  69. package/skills/monkey-mini-app/references/contracts/tag-input.md +15 -0
  70. package/skills/monkey-mini-app/references/contracts/terminal.md +13 -0
  71. package/skills/monkey-mini-app/references/contracts/test-step-list.md +23 -0
  72. package/skills/monkey-mini-app/references/contracts/time-picker.md +18 -0
  73. package/skills/monkey-mini-app/references/contracts/time-range-picker.md +23 -0
  74. package/skills/monkey-mini-app/references/contracts/time-wheel.md +29 -0
  75. package/skills/monkey-mini-app/references/contracts/timeline.md +24 -0
  76. package/skills/monkey-mini-app/references/contracts/timezone-select.md +27 -0
  77. package/skills/monkey-mini-app/references/contracts/transfer.md +24 -0
  78. package/skills/monkey-mini-app/references/contracts/tree-view.md +23 -0
  79. package/skills/monkey-mini-app/references/contracts/trend-card.md +26 -0
  80. package/skills/monkey-mini-app/references/contracts/user-picker.md +24 -0
  81. package/skills/monkey-mini-app/references/ctx.md +137 -0
  82. package/skills/monkey-mini-app/references/history.md +10 -0
  83. package/skills/monkey-mini-app/references/icons.md +83 -0
  84. package/skills/monkey-mini-app/references/llm-json.md +69 -0
  85. package/skills/monkey-mini-app/references/loader.md +30 -0
  86. package/skills/monkey-mini-app/references/test.md +30 -0
  87. package/skills/monkey-mini-app/references/tools.md +21 -0
  88. package/skills/monkey-mini-app/references/troubleshoot.md +12 -0
  89. package/skills/monkey-mini-app/templates/README.md +24 -0
  90. package/skills/monkey-mini-app/templates/agentrun/main.api.ts +78 -0
  91. package/skills/monkey-mini-app/templates/agentrun/manifest.json +9 -0
  92. package/skills/monkey-mini-app/templates/agentrun/ui.tsx +125 -0
  93. package/skills/monkey-mini-app/templates/insights/lib/sample.ts +17 -0
  94. package/skills/monkey-mini-app/templates/insights/main.api.ts +101 -0
  95. package/skills/monkey-mini-app/templates/insights/manifest.json +9 -0
  96. package/skills/monkey-mini-app/templates/insights/ui.tsx +138 -0
  97. package/skills/monkey-mini-app/templates/jira/main.api.ts +115 -0
  98. package/skills/monkey-mini-app/templates/jira/manifest.json +9 -0
  99. package/skills/monkey-mini-app/templates/jira/ui.tsx +318 -0
  100. package/skills/monkey-mini-app/templates/minimal/main.api.ts +16 -0
  101. package/skills/monkey-mini-app/templates/minimal/manifest.json +8 -0
  102. package/skills/monkey-mini-app/templates/minimal/ui.tsx +74 -0
  103. package/skills/monkey-mini-app/templates/monitor/main.api.ts +91 -0
  104. package/skills/monkey-mini-app/templates/monitor/manifest.json +9 -0
  105. package/skills/monkey-mini-app/templates/monitor/ui.tsx +176 -0
  106. package/skills/monkey-mini-app/templates/review/main.api.ts +68 -0
  107. package/skills/monkey-mini-app/templates/review/manifest.json +9 -0
  108. package/skills/monkey-mini-app/templates/review/ui.tsx +159 -0
  109. package/skills/monkey-mini-app/templates/todo/main.api.ts +83 -0
  110. package/skills/monkey-mini-app/templates/todo/manifest.json +8 -0
  111. package/skills/monkey-mini-app/templates/todo/ui.tsx +177 -0
  112. package/tsup.config.ts +68 -0
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "com.example.agentrun",
3
+ "name": "任务执行器",
4
+ "description": "让模型干一个多步活,实时展示过程,可取消",
5
+ "version": "0.1.0",
6
+ "entry": "ui.tsx",
7
+ "theme": { "followsHost": true },
8
+ "acronym": "RW"
9
+ }
@@ -0,0 +1,125 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ import {
4
+ ActivityFeed,
5
+ AppShell,
6
+ Badge,
7
+ Button,
8
+ Card,
9
+ CardContent,
10
+ CardDescription,
11
+ CardHeader,
12
+ CardTitle,
13
+ Icon,
14
+ Input,
15
+ PageHeader,
16
+ Stepper,
17
+ } from "@monkey-mini-app/ui";
18
+ import { useDashboardApi } from "@monkeyagent/host";
19
+
20
+ type Step = { phase: string; name?: string; turn?: number; text?: string; at: number };
21
+ type Run = { goal: string; status: string; steps: Step[]; result: string; startedAt: number };
22
+ type TimelineItem = { id: string; title: string; description?: string; time?: string };
23
+
24
+ export default function Ui() {
25
+ const { call } = useDashboardApi();
26
+ const [goal, setGoal] = useState("");
27
+ const [run, setRun] = useState<Run | null>(null);
28
+ const [running, setRunning] = useState(false);
29
+ const [error, setError] = useState<string | null>(null);
30
+
31
+ // ⭐ 关键:UI 不直接拿 agent 事件 —— 每 300ms 轮询 runStatus(后端 onEvent 已写进 storage)
32
+ useEffect(() => {
33
+ const poll = async () => {
34
+ const r = (await call("runStatus", {})) as Run;
35
+ setRun(r);
36
+ setRunning(r?.status === "running");
37
+ };
38
+ void poll();
39
+ const id = setInterval(() => void poll(), 300);
40
+ return () => clearInterval(id);
41
+ }, [call]);
42
+
43
+ async function start() {
44
+ setError(null);
45
+ try {
46
+ await call("start", { goal });
47
+ } catch (e) {
48
+ setError(String((e as Error)?.message || e));
49
+ }
50
+ }
51
+
52
+ const items: TimelineItem[] = (run?.steps ?? []).map((s, i) => ({
53
+ id: String(i),
54
+ title: s.phase === "tool" ? `调用工具 ${s.name ?? ""}` : s.phase === "turn" ? `第 ${s.turn} 轮` : s.phase === "done" ? "完成" : s.phase,
55
+ description: s.text ?? (s.phase === "tool" ? "结束" : undefined),
56
+ time: new Date(s.at).toLocaleTimeString("zh-CN"),
57
+ }));
58
+ const statusTone = run?.status === "done" ? "default" : run?.status === "error" || run?.status === "cancelled" ? "destructive" : "secondary";
59
+
60
+ return (
61
+ <AppShell header={<PageHeader title="任务执行器" description="模型跑多步活 · 实时过程 · 可取消" />}>
62
+ <div className="flex h-full min-h-0 max-w-3xl flex-col gap-3 overflow-y-auto">
63
+ <Card>
64
+ <CardHeader>
65
+ <CardTitle>目标</CardTitle>
66
+ <CardDescription>给一个多步目标,模型会拆解并用工具逐步完成</CardDescription>
67
+ </CardHeader>
68
+ <CardContent className="flex flex-col gap-2">
69
+ <Input value={goal} placeholder="例如:读 packages/ui/src 下组件,总结导出清单写到 storage" onChange={(e) => setGoal(e.target.value)} />
70
+ <div className="flex items-center gap-2">
71
+ <Button onClick={() => void start()} disabled={running || !goal.trim()}>
72
+ <Icon.Play size={16} strokeWidth={2} /> 开始
73
+ </Button>
74
+ {running && (
75
+ <Button variant="outline" onClick={() => void call("cancel", {})}>
76
+ <Icon.Square size={16} strokeWidth={2} /> 取消
77
+ </Button>
78
+ )}
79
+ <div className="ml-auto">
80
+ <Badge variant={statusTone}>{run?.status ?? "idle"}</Badge>
81
+ </div>
82
+ </div>
83
+ {error && <p className="text-sm" style={{ color: "var(--destructive)" }}>{error}</p>}
84
+ </CardContent>
85
+ </Card>
86
+
87
+ {run && (
88
+ <>
89
+ {/* ⭐ 关键:用 Stepper 展示「进行到第几步 + 每步状态」 */}
90
+ <Card>
91
+ <CardHeader><CardTitle>进度</CardTitle></CardHeader>
92
+ <CardContent>
93
+ <Stepper
94
+ steps={[
95
+ { id: "s1", title: "理解" },
96
+ { id: "s2", title: "执行" },
97
+ { id: "s3", title: "总结" },
98
+ ]}
99
+ current={run.steps.length >= 2 ? 2 : run.steps.length}
100
+ orientation="horizontal"
101
+ />
102
+ </CardContent>
103
+ </Card>
104
+
105
+ <Card className="min-h-0 flex-1 overflow-hidden">
106
+ <CardHeader><CardTitle>过程</CardTitle><CardDescription>工具调用 / 轮次时间线</CardDescription></CardHeader>
107
+ <CardContent className="max-h-72 overflow-y-auto">
108
+ <ActivityFeed items={items} />
109
+ </CardContent>
110
+ </Card>
111
+
112
+ {run.result && (
113
+ <Card>
114
+ <CardHeader><CardTitle>结果</CardTitle></CardHeader>
115
+ <CardContent>
116
+ <pre className="whitespace-pre-wrap text-sm">{run.result}</pre>
117
+ </CardContent>
118
+ </Card>
119
+ )}
120
+ </>
121
+ )}
122
+ </div>
123
+ </AppShell>
124
+ );
125
+ }
@@ -0,0 +1,17 @@
1
+ export type FeedItem = { title: string; link?: string };
2
+
3
+ // 内置样例源:默认不走网络,保证离线也能跑起来(教"采样走通全链路")
4
+ export const SAMPLE_ITEMS: FeedItem[] = [
5
+ { title: "TypeScript 5.7 发布:更快的类型检查与更好的模块解析", link: "https://example.com/ts57" },
6
+ { title: "Vite 6 默认启用 Rolldown 路线图", link: "https://example.com/vite6" },
7
+ { title: "React 19:Actions 与 useOptimistic 正式稳定", link: "https://example.com/react19" },
8
+ { title: "CSS 原生嵌套全面落地", link: "https://example.com/css-nest" },
9
+ { title: "Node.js 24 默认启用 SQLite 内置模块", link: "https://example.com/node24" },
10
+ { title: "WebGPU 在 Safari 的进展与局限", link: "https://example.com/webgpu-safari" },
11
+ { title: "Rust 1.85:async 生成器仍在打磨", link: "https://example.com/rust185" },
12
+ { title: "Bun 逐步兼容 npm 生态", link: "https://example.com/bun" },
13
+ { title: "新一版 ECMAScript 提案:RegExp 增强", link: "https://example.com/es-proposal" },
14
+ { title: "AI 编程助手对代码质量的实证研究", link: "https://example.com/ai-code" },
15
+ { title: "PostgreSQL 18 的并行查询改进", link: "https://example.com/pg18" },
16
+ { title: "移动端 3D 渲染的实用技巧", link: "https://example.com/mobile3d" },
17
+ ];
@@ -0,0 +1,101 @@
1
+ import { defineDashboard } from "@monkeyagent/dashboard";
2
+
3
+ import { type FeedItem, SAMPLE_ITEMS } from "./lib/sample";
4
+
5
+ type Digest = { headline: string; bullets: string[] };
6
+ type Payload = { items: FeedItem[]; digest: Digest | null; at: number };
7
+ type Progress = { running: boolean; step: string; done: number; total: number; error?: string };
8
+
9
+ const DIGEST_SCHEMA = {
10
+ type: "object",
11
+ properties: {
12
+ headline: { type: "string" },
13
+ bullets: { type: "array", items: { type: "string" } },
14
+ },
15
+ required: ["headline", "bullets"],
16
+ };
17
+
18
+ function parseDigest(raw: string): Digest {
19
+ let value: { headline?: unknown; bullets?: unknown };
20
+ try {
21
+ value = JSON.parse(raw);
22
+ } catch {
23
+ throw new Error("模型没有返回 JSON:" + String(raw).slice(0, 180));
24
+ }
25
+ const headline = String(value?.headline ?? "").trim();
26
+ const bullets = Array.isArray(value?.bullets)
27
+ ? value.bullets.map((b) => String(b).trim()).filter(Boolean).slice(0, 3)
28
+ : [];
29
+ if (!headline || bullets.length !== 3) throw new Error("摘要格式不对");
30
+ return { headline, bullets };
31
+ }
32
+
33
+ // ⭐ 关键:长耗时任务 —— 只跑采样走通全链路(这里取前 8 条),批次设上限,
34
+ // 并在每步之间检查 ctx.signal(用户点停止即中断)。进度经 storage 暴露给 UI 轮询。
35
+ async function runRefresh(ctx): Promise<Payload> {
36
+ const items = SAMPLE_ITEMS.slice(0, 8);
37
+ const report = async (partial: Partial<Progress>) => {
38
+ await ctx.storage.set("progress", { running: true, step: "refresh", done: 0, total: 3, ...partial });
39
+ };
40
+ try {
41
+ // 1) 采样拉取(这里用内置样例;真实场景换成 ctx.http 拉 RSS)
42
+ await report({ step: "fetch·sample 8/8", done: 1 });
43
+ // ⭐ 关键:每步之间检查取消信号
44
+ if (ctx.signal?.aborted) throw new Error("cancelled");
45
+ await sleep(300, ctx.signal);
46
+
47
+ // 2) 模型摘要(schema 软约束"只回 JSON";返回仍是 string,再 JSON.parse)
48
+ await report({ step: "summarize#1/1", done: 2 });
49
+ const raw = await ctx.llm(
50
+ "根据这些标题写一条中文 headline,以及恰好 3 条中文 bullets。不要前言:\n" + items.map((i) => "- " + i.title).join("\n"),
51
+ { schema: DIGEST_SCHEMA },
52
+ );
53
+ if (ctx.signal?.aborted) throw new Error("cancelled");
54
+ const digest = parseDigest(raw);
55
+
56
+ const payload: Payload = { items, digest, at: Date.now() };
57
+ await ctx.storage.set("latest", payload);
58
+ await ctx.storage.set("progress", { running: false, step: "done", done: 3, total: 3 });
59
+ return payload;
60
+ } catch (cause) {
61
+ const message = cause instanceof Error ? cause.message : String(cause);
62
+ await ctx.storage.set("progress", { running: false, step: "failed", done: 0, total: 3, error: message });
63
+ throw new Error("摘要失败:" + message);
64
+ }
65
+ }
66
+
67
+ export default defineDashboard({
68
+ name: "今日摘要",
69
+ description: "拉取公开头条,整理成三条要点",
70
+ api: {
71
+ async latest(ctx): Promise<Payload> {
72
+ return (await ctx.storage.get("latest")) || { items: SAMPLE_ITEMS, digest: null, at: 0 };
73
+ },
74
+
75
+ // fire-and-forget:立即返回"已启动",实际工作在后台,UI 用 scanStatus 轮询
76
+ async scan(ctx) {
77
+ void runRefresh(ctx);
78
+ return { ok: true };
79
+ },
80
+
81
+ async scanStatus(ctx): Promise<Progress> {
82
+ return (await ctx.storage.get("progress")) || { running: false, step: "idle", done: 0, total: 3 };
83
+ },
84
+
85
+ // 也可直接 await 调用(不用后台轮询)
86
+ async refresh(ctx) {
87
+ return runRefresh(ctx);
88
+ },
89
+ },
90
+ });
91
+
92
+ function sleep(ms: number, signal?: AbortSignal) {
93
+ return new Promise<void>((resolve) => {
94
+ if (signal?.aborted) return resolve();
95
+ const t = setTimeout(resolve, ms);
96
+ signal?.addEventListener("abort", () => {
97
+ clearTimeout(t);
98
+ resolve();
99
+ }, { once: true });
100
+ });
101
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "com.example.insights",
3
+ "name": "今日摘要",
4
+ "description": "公开头条 + 模型整理成三条要点",
5
+ "version": "0.1.0",
6
+ "entry": "ui.tsx",
7
+ "theme": { "followsHost": true },
8
+ "acronym": "ZY"
9
+ }
@@ -0,0 +1,138 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ import {
4
+ AppShell,
5
+ Badge,
6
+ Button,
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ DetailPanel,
13
+ Icon,
14
+ PageHeader,
15
+ Progress,
16
+ } from "@monkey-mini-app/ui";
17
+ import { useDashboardApi } from "@monkeyagent/host";
18
+
19
+ type Digest = { headline: string; bullets: string[] };
20
+ type Item = { title: string; link?: string };
21
+ type Payload = { items: Item[]; digest: Digest | null; at: number };
22
+ type Progress = { running: boolean; step: string; done: number; total: number; error?: string };
23
+
24
+ export default function Ui() {
25
+ const { call } = useDashboardApi();
26
+ const [data, setData] = useState<Payload | null>(null);
27
+ const [progress, setProgress] = useState<Progress | null>(null);
28
+ const [selected, setSelected] = useState<Item | null>(null);
29
+ const [error, setError] = useState<string | null>(null);
30
+
31
+ const load = async () => setData((await call("latest", {})) as Payload);
32
+
33
+ async function run() {
34
+ setError(null);
35
+ try {
36
+ await call("scan", {}); // fire-and-forget 启动,立即返回
37
+ // ⭐ 关键:长任务进度 —— UI 轮询 scanStatus 直到 running=false
38
+ const iv = setInterval(async () => {
39
+ const p = (await call("scanStatus", {})) as Progress;
40
+ setProgress(p);
41
+ if (!p.running) {
42
+ clearInterval(iv);
43
+ await load();
44
+ }
45
+ }, 300);
46
+ // 兜底:一段时间后强制停轮询(防止 progress 卡在 running)
47
+ setTimeout(() => clearInterval(iv), 60_000);
48
+ } catch (e) {
49
+ setError(String((e as Error)?.message || e));
50
+ }
51
+ }
52
+
53
+ useEffect(() => {
54
+ void load();
55
+ }, [call]);
56
+
57
+ const digest = data?.digest;
58
+ const items = data?.items ?? [];
59
+ const pct = progress && progress.total ? Math.round((progress.done / progress.total) * 100) : 0;
60
+
61
+ return (
62
+ <AppShell
63
+ header={
64
+ <PageHeader
65
+ title="今日摘要"
66
+ description="公开头条 · 模型整理成三条要点"
67
+ actions={<Button disabled={progress?.running} onClick={() => void run()}><Icon.Sparkles size={16} strokeWidth={2} /> 整理</Button>}
68
+ />
69
+ }
70
+ >
71
+ <div className="grid h-full min-h-0 grid-cols-1 gap-3 md:grid-cols-[1fr_360px]">
72
+ {/* 主从:左列表 */}
73
+ <Card className="min-h-0 overflow-hidden">
74
+ <CardHeader>
75
+ <CardTitle>来源</CardTitle>
76
+ <CardDescription>点击看详情</CardDescription>
77
+ {progress?.running && (
78
+ <div className="flex items-center gap-2">
79
+ <Progress value={pct} className="flex-1" />
80
+ <span className="text-xs text-muted-foreground">{progress.step}</span>
81
+ </div>
82
+ )}
83
+ {error && <p className="text-sm" style={{ color: "var(--destructive)" }}>{error}</p>}
84
+ </CardHeader>
85
+ <CardContent className="flex flex-col min-h-0 flex-1 gap-1 overflow-y-auto">
86
+ {items.map((it, i) => (
87
+ <button
88
+ key={it.title + i}
89
+ onClick={() => setSelected(it)}
90
+ className="flex items-center justify-between gap-2 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-muted/50"
91
+ >
92
+ <span className="min-w-0 flex-1 truncate">{it.title}</span>
93
+ <Icon.ChevronRight size={16} strokeWidth={2} className="shrink-0 text-muted-foreground" />
94
+ </button>
95
+ ))}
96
+ </CardContent>
97
+ </Card>
98
+
99
+ {/* 主从:右详情(选中某条才开,关掉即清) */}
100
+ <DetailPanel
101
+ open={Boolean(selected)}
102
+ onOpenChange={(o) => { if (!o) setSelected(null); }}
103
+ title={selected?.title ?? digest?.headline ?? "摘要"}
104
+ description={selected ? selected.title : "模型整理的要点"}
105
+ >
106
+ <div className="flex flex-col gap-3">
107
+ <Card>
108
+ <CardHeader>
109
+ <CardTitle>{digest?.headline || "还没有摘要"}</CardTitle>
110
+ <CardDescription>{data?.at ? "更新于 " + new Date(data.at).toLocaleString("zh-CN") : "点「整理」用模型生成"}</CardDescription>
111
+ </CardHeader>
112
+ <CardContent>
113
+ {digest?.bullets?.length ? (
114
+ <ol className="flex flex-col gap-2 pl-5 m-0">
115
+ {digest.bullets.map((b, i) => (
116
+ <li key={i} className="text-sm leading-relaxed">{b}</li>
117
+ ))}
118
+ </ol>
119
+ ) : (
120
+ <span className="text-sm text-muted-foreground">还没有要点,先点「整理」。</span>
121
+ )}
122
+ </CardContent>
123
+ </Card>
124
+ {selected && (
125
+ <Card>
126
+ <CardHeader><CardTitle className="text-sm">选中条目</CardTitle></CardHeader>
127
+ <CardContent>
128
+ <p className="text-sm">{selected.title}</p>
129
+ {selected.link ? <a href={selected.link} target="_blank" rel="noreferrer" className="text-xs text-primary hover:underline w-fit">阅读原文</a> : null}
130
+ </CardContent>
131
+ </Card>
132
+ )}
133
+ </div>
134
+ </DetailPanel>
135
+ </div>
136
+ </AppShell>
137
+ );
138
+ }
@@ -0,0 +1,115 @@
1
+ import { defineDashboard } from "@monkeyagent/dashboard";
2
+
3
+ // ⭐ 关键:列表/看板数据项的"key"字段很重要 —— 拖拽/更新都用它定位(还记得 idKey="key" 的坑)。
4
+ // Jira 仿真:改状态/负责人/优先级、加评论、AI 记工时。
5
+ export type Issue = {
6
+ key: string; // 例如 "BUG-104"
7
+ type: "Story" | "Task" | "Bug" | "Epic";
8
+ summary: string;
9
+ status: "To Do" | "In Progress" | "Done" | "Blocked";
10
+ priority: "P0" | "P1" | "P2" | "P3";
11
+ assignee: string;
12
+ reporter: string;
13
+ points: number;
14
+ comments: { author: string; text: string; at: number }[];
15
+ createdAt: number;
16
+ updatedAt: number;
17
+ };
18
+
19
+ const SEED: Issue[] = [
20
+ { key: "BUG-104", type: "Bug", summary: "看板拖拽到列后状态未持久化", status: "In Progress", priority: "P0", assignee: "陈", reporter: "李", points: 3, comments: [{ author: "陈", text: "复现:跨列拖拽后刷新丢失", at: Date.now() - 864e5 }], createdAt: Date.now() - 3 * 864e5, updatedAt: Date.now() - 3600e3 },
21
+ { key: "STORY-21", type: "Story", summary: "支持按负责人筛选看板", status: "To Do", priority: "P1", assignee: "王", reporter: "陈", points: 5, comments: [], createdAt: Date.now() - 2 * 864e5, updatedAt: Date.now() - 2 * 864e5 },
22
+ { key: "TASK-88", type: "Task", summary: "表格视图增加优先级排序", status: "Done", priority: "P2", assignee: "李", reporter: "王", points: 2, comments: [], createdAt: Date.now() - 6 * 864e5, updatedAt: Date.now() - 8 * 3600e3 },
23
+ { key: "BUG-77", type: "Bug", summary: "详情弹层评论不刷新最新一条", status: "Blocked", priority: "P1", assignee: "陈", reporter: "李", points: 3, comments: [], createdAt: Date.now() - 5 * 864e5, updatedAt: Date.now() - 864e5 },
24
+ { key: "STORY-9", type: "Story", summary: "状态汇总色板支持自定义主题", status: "In Progress", priority: "P1", assignee: "王", reporter: "陈", points: 8, comments: [], createdAt: Date.now() - 1 * 864e5, updatedAt: Date.now() - 3600e3 },
25
+ { key: "TASK-5", type: "Task", summary: "AI 工时记录入口", status: "To Do", priority: "P0", assignee: "陈", reporter: "李", points: 2, comments: [], createdAt: Date.now() - 864e5, updatedAt: Date.now() - 864e5 },
26
+ ];
27
+
28
+ async function load(ctx): Promise<Issue[]> {
29
+ const issues = await ctx.storage.table("issues").get("all");
30
+ return Array.isArray(issues) ? issues : SEED;
31
+ }
32
+ async function save(ctx, issues: Issue[]) {
33
+ await ctx.storage.table("issues").set("all", issues);
34
+ }
35
+ function touch(i: Issue): Issue {
36
+ return { ...i, updatedAt: Date.now() };
37
+ }
38
+
39
+ export default defineDashboard({
40
+ name: "Jira 看板",
41
+ description: "看板/表格双视图 · 状态与评论编辑 · AI 记工时",
42
+ api: {
43
+ async list(ctx) {
44
+ const issues = await load(ctx);
45
+ return { issues };
46
+ },
47
+ // ⭐ 关键:乐观更新 + 持久化 —— 拖拽/改字段先改内存再落库;UI 拿 key 定位
48
+ async update(ctx, args?: { key?: string; patch?: Partial<Issue> }) {
49
+ const key = args?.key;
50
+ if (!key) throw new Error("缺少 key");
51
+ const issues = await load(ctx);
52
+ let found = false;
53
+ const next = issues.map((i) => {
54
+ if (i.key !== key) return i;
55
+ found = true;
56
+ return touch({ ...i, ...(args?.patch ?? {}) });
57
+ });
58
+ if (!found) throw new Error("issue 不存在:" + key);
59
+ await save(ctx, next);
60
+ return next.find((i) => i.key === key);
61
+ },
62
+ async comment(ctx, args?: { key?: string; text?: string }) {
63
+ const text = String(args?.text ?? "").trim();
64
+ if (!text) throw new Error("评论不能为空");
65
+ const key = args?.key;
66
+ const issues = await load(ctx);
67
+ let found = false;
68
+ const next = issues.map((i) => {
69
+ if (i.key !== key) return i;
70
+ found = true;
71
+ return touch({ ...i, comments: [...i.comments, { author: "我", text, at: Date.now() }] });
72
+ });
73
+ if (!found) throw new Error("issue 不存在");
74
+ await save(ctx, next);
75
+ return next.find((i) => i.key === key);
76
+ },
77
+ // 状态汇总:按 status 计数(简约色板用)
78
+ async summary(ctx) {
79
+ const issues = await load(ctx);
80
+ const byStatus: Record<string, number> = {};
81
+ for (const i of issues) byStatus[i.status] = (byStatus[i.status] || 0) + 1;
82
+ return { byStatus, total: issues.length };
83
+ },
84
+ // ⭐ 关键:AI 记工时 —— 用 ctx.llm(schema) 把 issue 的近期变动生成一条 worklog 草稿,用户确认后再存
85
+ async worklogDraft(ctx, args?: { key?: string }) {
86
+ const key = args?.key;
87
+ const issues = await load(ctx);
88
+ const issue = issues.find((i) => i.key === key);
89
+ if (!issue) throw new Error("issue 不存在");
90
+ const activity = issue.comments.map((c) => `${c.author}: ${c.text}`).join(";") || "(无评论)";
91
+ const raw = await ctx.llm(
92
+ `为 Jira ${issue.key}「${issue.summary}」写一条简洁中文 worklog(当天在做什么、进展、下一步)。状态: ${issue.status}。近期动态: ${activity}`,
93
+ { schema: { type: "object", properties: { note: { type: "string" } }, required: ["note"] } },
94
+ );
95
+ const parsed = JSON.parse(raw) as { note?: string };
96
+ return { note: String(parsed.note ?? "").trim() || issue.summary };
97
+ },
98
+ // 确认后存成一条评论(worklog 前缀)
99
+ async logWork(ctx, args?: { key?: string; note?: string; minutes?: number }) {
100
+ const key = args?.key;
101
+ const minutes = Number(args?.minutes ?? 0) || 0;
102
+ const note = String(args?.note ?? "").trim();
103
+ const issues = await load(ctx);
104
+ let found = false;
105
+ const next = issues.map((i) => {
106
+ if (i.key !== key) return i;
107
+ found = true;
108
+ return touch({ ...i, comments: [...i.comments, { author: "我", text: `[worklog] ${minutes}min · ${note}`, at: Date.now() }] });
109
+ });
110
+ if (!found) throw new Error("issue 不存在");
111
+ await save(ctx, next);
112
+ return next.find((i) => i.key === key);
113
+ },
114
+ },
115
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "com.example.jira",
3
+ "name": "Jira 看板",
4
+ "description": "看板/表格双视图 · 状态与评论编辑 · AI 记工时",
5
+ "version": "0.1.0",
6
+ "entry": "ui.tsx",
7
+ "theme": { "followsHost": true },
8
+ "acronym": "JB"
9
+ }