@liem-opc/chat-to-article-mcp-ui 0.1.0 → 0.1.1

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 CHANGED
@@ -24,6 +24,7 @@
24
24
  | `review_writing_plan` | 文体标签、读者、目的、七条偏好轴、备选行文方案;每项都能批注 | 确认计划 / 让 agent 再想想 / 先不写 | 确认后写 `writing-plan.json` |
25
25
  | `review_claims` | 每条主张:采用 / 待定 / 有疑问 / 不要,可改字、批注、补一条;疑似密钥要勾选「已处理」;提示哪些块会因此过期 | 提交决定 / 让 agent 再查 | 调用时把新主张以 candidate 写入 `claims.jsonl`;提交时写入决定和 `materials.json` |
26
26
  | `review_outline` | 章节排序、增删、主张和图的分配、块拆分;未覆盖的主张 | 确认大纲 / 让 agent 重排 | 确认后写 `outline.json` 和图位 |
27
+ | `review_variants` | 同一块的 2–4 种笔调并排,标出「按你的偏好」那一版;每版可写喜欢或不喜欢的地方 | 就用这版 / 都不满意,换一组 / 稍后再说 | 调用时不写文件;选定后把那版以 draft 写入 `blocks/<id>.md`,返回 `pick-variant` 命令 |
27
28
  | `review_block` | 所见即所得的修订模式:直接改字留痕(插入下划线、删除线),逐处或全部接受/拒绝;选中文字加批注;词表实时检查;前后块只读;与上一版的差异 | 接受这块 / 按批注修改 / 稍后再说 | 调用时以 draft 写入 `blocks/<id>.md`;提交时保存你的修改 |
28
29
  | `review_figure` | Mermaid 实时预览、源文件、图注、替代文字 | 接受这张图 / 让 agent 改 / 稍后再说 | 调用时写图源;提交时更新 `figures/manifest.json` |
29
30
  | `review_style_candidates` | 每条候选:写进风格 / 再观察 / 不是我的习惯,措辞可改 | 提交 / 稍后再说 | 不写文件,返回 `bump-style` 命令 |
package/dist/cli.js CHANGED
@@ -20,6 +20,7 @@ var ACTIONS = [
20
20
  "review_writing_plan",
21
21
  "review_claims",
22
22
  "review_outline",
23
+ "review_variants",
23
24
  "review_block",
24
25
  "review_figure",
25
26
  "review_style_candidates"
@@ -146,6 +147,28 @@ var reviewOutlineResult = z.object({
146
147
  comments: z.array(z.object({ sectionId: z.string(), text: z.string() })),
147
148
  note: z.string().optional()
148
149
  });
150
+ var variantDraft = z.object({
151
+ id: z.string().min(1).describe("\u7248\u672C id\uFF0C\u4F8B\u5982 A\u3001B\u3001C"),
152
+ voice: z.string().min(1).describe("\u5185\u7F6E\u7B14\u8C03 id\uFF08cta.py presets \u5217\u51FA\uFF09\u3001profile\uFF08\u6309\u4F5C\u8005\u7684\u504F\u597D\u5199\uFF09\u6216 custom"),
153
+ label: z.string().min(1).describe("\u7ED9\u4F5C\u8005\u770B\u7684\u540D\u5B57\uFF0C\u4F8B\u5982\u300C\u6309\u4F60\u7684\u504F\u597D\u300D\u300C\u8DDF\u540C\u4E8B\u590D\u76D8\u300D"),
154
+ summary: z.string().optional().describe("\u4E00\u53E5\u8BDD\u8BF4\u8FD9\u7248\u548C\u5176\u4ED6\u7248\u5DEE\u5728\u54EA"),
155
+ body: z.string().min(1).describe("\u8FD9\u4E00\u7248\u7684 Markdown \u6B63\u6587")
156
+ });
157
+ var reviewVariantsInput = {
158
+ article: articlePath,
159
+ blockId: z.string().min(1).describe("\u5927\u7EB2\u91CC\u7684\u5757 id\uFF0C\u4F8B\u5982 b001"),
160
+ variants: z.array(variantDraft).min(2).max(4).describe("\u540C\u4E00\u5757\u3001\u540C\u6837\u4E3B\u5F20\u3001\u4E0D\u540C\u7B14\u8C03\u7684\u51E0\u7248\uFF1B\u6709\u504F\u597D\u6750\u6599\u65F6\u5176\u4E2D\u4E00\u7248\u7684 voice \u4E3A profile"),
161
+ claimIds: z.array(z.string()).optional(),
162
+ figureIds: z.array(z.string()).optional(),
163
+ context: z.string().optional().describe("\u4E3A\u4EC0\u4E48\u51FA\u8FD9\u51E0\u7248\uFF0C\u4F8B\u5982\u300C\u8FD8\u6CA1\u9009\u5B9A\u7B14\u8C03\u300D")
164
+ };
165
+ var reviewVariantsArgs = z.object(reviewVariantsInput);
166
+ var reviewVariantsResult = z.object({
167
+ decision: z.enum(["pick", "regenerate", "later"]),
168
+ chosenId: z.string().optional(),
169
+ feedback: z.array(z.object({ variantId: z.string(), text: z.string() })),
170
+ note: z.string().optional()
171
+ });
149
172
  var reviewBlockInput = {
150
173
  article: articlePath,
151
174
  blockId: z.string().min(1).describe("\u5927\u7EB2\u91CC\u7684\u5757 id\uFF0C\u4F8B\u5982 b001"),
@@ -567,6 +590,9 @@ function pinnedProfile(meta) {
567
590
  if (!meta || voice.profile.revision === meta.styleRevision) return voice.profile;
568
591
  return voice.history.find((h) => h.revision === meta.styleRevision)?.profile ?? voice.profile;
569
592
  }
593
+ function articlePreferences(dir) {
594
+ return readJsonl(join7(dir, "preferences.jsonl")).filter((p) => p.status === "active" && p.kind !== "voice").map((p) => ({ id: p.id, text: p.text }));
595
+ }
570
596
  function outlineBlockIds(dir) {
571
597
  return loadOutline(dir).sections.flatMap((s) => s.blockIds);
572
598
  }
@@ -928,6 +954,74 @@ var reviewOutline = {
928
954
  );
929
955
  }
930
956
  };
957
+ var LEARN_HINT = "\u5148\u628A\u4FEE\u8BA2\u548C\u6279\u6CE8\u91CC\u80FD\u63A8\u5E7F\u5230\u522B\u7684\u5757\u7684\u5199\u6CD5\uFF08\u7528\u8BCD\u3001\u53E5\u5F0F\u3001\u8BED\u6C14\u3001\u8282\u594F\u3001\u4EBA\u79F0\u3001\u6BB5\u843D\u5904\u7406\uFF09\u5F52\u7EB3\u6210 0\u20133 \u6761\u504F\u597D\uFF0C\u9010\u6761\u8FD0\u884C learn \u8BB0\u4E0B\uFF0C\u5E76\u7528\u4E00\u53E5\u8BDD\u544A\u8BC9\u7528\u6237\u8BB0\u4E86\u4EC0\u4E48\uFF1B\u53EA\u5BF9\u8FD9\u4E00\u5904\u6210\u7ACB\u7684\u4FEE\u6539\u4E0D\u8BB0\u3002";
958
+ var reviewVariants = {
959
+ kind: "review_variants",
960
+ description: "\u672C\u7BC7\u8FD8\u6CA1\u9009\u5B9A\u7B14\u8C03\u65F6\uFF0C\u628A\u540C\u4E00\u5757\u7684 2\u20134 \u7248\uFF08\u540C\u6837\u7684\u4E3B\u5F20\u3001\u4E0D\u540C\u7B14\u8C03\uFF09\u5E76\u6392\u5D4C\u5165\u5BF9\u8BDD\uFF0C\u8BA9\u7528\u6237\u6311\u4E00\u7248\uFF0C\u5E76\u5BF9\u5404\u7248\u5199\u559C\u6B22\u6216\u4E0D\u559C\u6B22\u7684\u5730\u65B9\u3002\u8C03\u7528\u65F6\u4E0D\u5199\u6587\u4EF6\uFF1B\u7528\u6237\u9009\u5B9A\u540E\uFF0C\u9009\u4E2D\u7684\u6B63\u6587\u4EE5 draft \u5199\u5165 blocks/<id>.md\u3002\u8D77\u8349\u95E8\u7981\u4E0E check-draft \u76F8\u540C\u3002",
961
+ args: reviewVariantsArgs,
962
+ result: reviewVariantsResult,
963
+ prepare(input) {
964
+ const dir = resolveArticle(input.article);
965
+ const meta = requireArticle(dir);
966
+ const gate = checkDraft(dir, input.blockId);
967
+ if (gate) throw new UserFacingError(gate);
968
+ const ids = new Set(input.variants.map((v) => v.id));
969
+ if (ids.size !== input.variants.length) throw new UserFacingError("\u7248\u672C id \u4E0D\u80FD\u91CD\u590D\u3002");
970
+ const section = sectionOfBlock(dir, input.blockId);
971
+ const claims = new Map(loadClaims(dir).map((c) => [c.id, c]));
972
+ const outlineIds = loadOutline(dir).sections.flatMap((s) => s.blockIds);
973
+ const prevId = outlineIds[outlineIds.indexOf(input.blockId) - 1];
974
+ const prev = prevId ? loadBlock(dir, prevId) : null;
975
+ const context = {
976
+ sectionTitle: section?.title ?? null,
977
+ claims: (input.claimIds ?? section?.claimIds ?? []).map((id) => ({
978
+ id,
979
+ text: claims.get(id)?.text ?? "\uFF08\u4E3B\u5F20\u7F3A\u5931\uFF09",
980
+ status: claims.get(id)?.status ?? "missing"
981
+ })),
982
+ prev: prev?.exists ? { id: prev.id, body: prev.body } : null,
983
+ preferences: articlePreferences(dir)
984
+ };
985
+ return { title: `\u9009\u7B14\u8C03\uFF1A${meta.title} \xB7 ${context.sectionTitle ?? ""} \xB7 ${input.blockId}`, article: dir, context };
986
+ },
987
+ submit(input, result) {
988
+ const dir = resolveArticle(input.article);
989
+ const cta = ctaCommand(dir);
990
+ const label = (id) => {
991
+ const v = input.variants.find((x) => x.id === id);
992
+ return v ? `${v.id}\u300C${v.label}\u300D` : id;
993
+ };
994
+ const feedback = result.feedback.filter((f) => f.text.trim()).map((f) => `- ${label(f.variantId)}\uFF1A${f.text.trim()}`);
995
+ if (result.decision === "later") return lines(`\u7528\u6237\u6682\u65F6\u4E0D\u9009 ${input.blockId} \u7684\u7248\u672C\uFF0C\u6CA1\u6709\u5199\u5165\u4EFB\u4F55\u6587\u4EF6\u3002`, note(result.note), ...feedback);
996
+ if (result.decision === "regenerate")
997
+ return lines(
998
+ `\u7528\u6237\u5BF9 ${input.blockId} \u7684\u51E0\u7248\u90FD\u4E0D\u6EE1\u610F\uFF0C\u6CA1\u6709\u5199\u5165\u4EFB\u4F55\u6587\u4EF6\u3002`,
999
+ feedback.length ? "\u7528\u6237\u5BF9\u5404\u7248\u7684\u8BC4\u4EF7\uFF1A" : null,
1000
+ ...feedback,
1001
+ note(result.note),
1002
+ `\u628A\u8BC4\u4EF7\u91CC\u80FD\u63A8\u5E7F\u7684\u5199\u6CD5\u7528 learn --source variant \u8BB0\u4E0B\uFF0C\u7136\u540E\u6309\u8BC4\u4EF7\u6362\u4E00\u7EC4\u7248\u672C\uFF0C\u518D\u8C03\u7528 review_variants\u3002`
1003
+ );
1004
+ const chosen = input.variants.find((v) => v.id === result.chosenId);
1005
+ if (!chosen) throw new UserFacingError("\u9009\u5B9A\u65F6\u5FC5\u987B\u7ED9\u51FA chosenId\u3002");
1006
+ writeBlockDraft(dir, input.blockId, chosen.body, {
1007
+ claimIds: input.claimIds,
1008
+ figureIds: input.figureIds,
1009
+ instruction: `\u7528\u6237\u4ECE\u591A\u7248\u4E2D\u9009\u4E86 ${chosen.label}`
1010
+ });
1011
+ const chosenFeedback = result.feedback.find((f) => f.variantId === chosen.id)?.text.trim();
1012
+ const voiceNote = [chosen.voice === "custom" ? chosen.summary ?? chosen.label : null, chosenFeedback, result.note?.trim()].filter(Boolean).join("\uFF1B");
1013
+ return lines(
1014
+ `\u7528\u6237\u9009\u4E86 ${label(chosen.id)}\uFF08\u7B14\u8C03 ${chosen.voice}\uFF09\uFF0C\u6B63\u6587\u5DF2\u4EE5 draft \u5199\u5165 blocks/${input.blockId}.md\u3002`,
1015
+ feedback.length ? "\u7528\u6237\u5BF9\u5404\u7248\u7684\u8BC4\u4EF7\uFF08\u6CA1\u9009\u4E2D\u7684\u7248\u672C\u4E5F\u8BF4\u660E\u4E86\u504F\u597D\uFF09\uFF1A" : null,
1016
+ ...feedback,
1017
+ note(result.note),
1018
+ "\u4E0B\u4E00\u6B65\uFF1A",
1019
+ `${cta} pick-variant --article ${q(dir)} --block ${input.blockId} --voice ${chosen.voice}${voiceNote ? ` --note ${q(voiceNote)}` : ""}`,
1020
+ `\u7136\u540E\u628A\u8BC4\u4EF7\u91CC\u80FD\u63A8\u5E7F\u7684\u5199\u6CD5\u9010\u6761\u7528 ${cta} learn --article ${q(dir)} --block ${input.blockId} --source variant --text "\u2026" \u8BB0\u4E0B\uFF0C`,
1021
+ "\u518D\u8C03\u7528 review_block \u628A\u9009\u4E2D\u7684\u8FD9\u7248\u4EA4\u7ED9\u7528\u6237\u7CBE\u4FEE\uFF08body \u7528\u9009\u4E2D\u7684\u6B63\u6587\uFF0C\u6309\u521A\u8BB0\u4E0B\u7684\u504F\u597D\u5148\u8C03\u597D\uFF09\u3002"
1022
+ );
1023
+ }
1024
+ };
931
1025
  function describeChanges(result) {
932
1026
  return result.changes.map((c) => {
933
1027
  const where = c.context ? `\uFF08${c.context}\uFF09` : "";
@@ -973,8 +1067,11 @@ var reviewBlock = {
973
1067
  prev: neighbor(idx - 1),
974
1068
  next: neighbor(idx + 1),
975
1069
  rules: lexiconRules(dir),
976
- styleRules: (profile?.rules ?? []).filter((r) => r.status === "active").map((r) => ({ id: r.id, text: r.text, strength: r.strength })),
977
- paragraph: profile?.paragraph ?? null,
1070
+ styleRules: [
1071
+ ...articlePreferences(dir).map((p) => ({ id: p.id, text: p.text, strength: "\u672C\u7BC7" })),
1072
+ ...(profile?.rules ?? []).filter((r) => r.status === "active").map((r) => ({ id: r.id, text: r.text, strength: r.strength }))
1073
+ ],
1074
+ paragraph: null,
978
1075
  styleRevision: meta.styleRevision ?? null
979
1076
  };
980
1077
  return { title: `${meta.title} \xB7 ${context.sectionTitle ?? ""} \xB7 ${input.blockId}`, article: dir, context };
@@ -1001,6 +1098,7 @@ var reviewBlock = {
1001
1098
  ...comments,
1002
1099
  note(result.note),
1003
1100
  `\u4E0B\u4E00\u6B65\uFF1A${cta} accept-block --article ${q(dir)} --block ${input.blockId}`,
1101
+ (changes.length || comments.length || result.note?.trim()) && `\u5199\u4E0B\u4E00\u5757\u4E4B\u524D\uFF0C${LEARN_HINT}\u547D\u4EE4\uFF1A${cta} learn --article ${q(dir)} --block ${input.blockId} --source edit --text "\u2026" --evidence "\u6539\u524D \u2192 \u6539\u540E"`,
1004
1102
  "\u7136\u540E\u53EA\u628A\u4E0B\u4E00\u5757\u4EA4\u7ED9\u7528\u6237\u770B\u3002"
1005
1103
  );
1006
1104
  return lines(
@@ -1010,9 +1108,11 @@ var reviewBlock = {
1010
1108
  comments.length ? "\u6279\u6CE8\uFF1A" : null,
1011
1109
  ...comments,
1012
1110
  note(result.note),
1111
+ LEARN_HINT,
1112
+ `\u547D\u4EE4\uFF1A${cta} learn --article ${q(dir)} --block ${input.blockId} --source annotation --text "\u2026" --evidence "\u6539\u524D \u2192 \u6539\u540E"`,
1013
1113
  "\u5F53\u524D\u6B63\u6587\uFF1A",
1014
1114
  result.finalBody.trim(),
1015
- "\u53EA\u6539\u8FD9\u4E00\u5757\uFF0C\u53EA\u7528\u8FD9\u5757 claimIds \u91CC\u5DF2\u63A5\u53D7\u7684\u4E3B\u5F20\uFF1B\u6539\u5B8C\u518D\u8C03\u7528 review_block\uFF08instruction \u5199\u660E\u6539\u4E86\u4EC0\u4E48\uFF09\u3002"
1115
+ "\u53EA\u6539\u8FD9\u4E00\u5757\uFF0C\u53EA\u7528\u8FD9\u5757 claimIds \u91CC\u5DF2\u63A5\u53D7\u7684\u4E3B\u5F20\uFF1B\u6539\u7684\u65F6\u5019\u4E5F\u7167\u521A\u8BB0\u4E0B\u7684\u504F\u597D\u8C03\u6574\u6574\u5757\uFF0C\u4E0D\u53EA\u6539\u6279\u6CE8\u6307\u5230\u7684\u5B57\u3002\u6539\u5B8C\u518D\u8C03\u7528 review_block\uFF08instruction \u5199\u660E\u6539\u4E86\u4EC0\u4E48\uFF09\u3002"
1016
1116
  );
1017
1117
  }
1018
1118
  };
@@ -1095,6 +1195,7 @@ var HANDLERS = {
1095
1195
  review_writing_plan: reviewWritingPlan,
1096
1196
  review_claims: reviewClaims,
1097
1197
  review_outline: reviewOutline,
1198
+ review_variants: reviewVariants,
1098
1199
  review_block: reviewBlock,
1099
1200
  review_figure: reviewFigure,
1100
1201
  review_style_candidates: reviewStyleCandidates
@@ -1110,6 +1211,7 @@ var VIEW_OF = {
1110
1211
  review_writing_plan: "review",
1111
1212
  review_claims: "review",
1112
1213
  review_outline: "review",
1214
+ review_variants: "review",
1113
1215
  review_block: "review",
1114
1216
  review_figure: "figure",
1115
1217
  review_style_candidates: "review"
@@ -1119,6 +1221,7 @@ var TITLES = {
1119
1221
  review_writing_plan: "\u5BA1\u9605\u5199\u4F5C\u8BA1\u5212",
1120
1222
  review_claims: "\u5BA1\u9605\u4E3B\u5F20",
1121
1223
  review_outline: "\u5BA1\u9605\u5927\u7EB2",
1224
+ review_variants: "\u591A\u7248\u6311\u7B14\u8C03",
1122
1225
  review_block: "\u5BA1\u9605\u6B63\u6587\u5757\uFF08\u4FEE\u8BA2\u6A21\u5F0F\uFF09",
1123
1226
  review_figure: "\u5BA1\u9605\u56FE",
1124
1227
  review_style_candidates: "\u786E\u8BA4\u98CE\u683C\u5019\u9009"
@@ -1327,7 +1430,7 @@ var SessionManager = class {
1327
1430
  };
1328
1431
 
1329
1432
  // src/server/cli.ts
1330
- var VERSION = true ? "0.1.0" : "dev";
1433
+ var VERSION = true ? "0.1.1" : "dev";
1331
1434
  var HELP = `chat-to-article-mcp-ui \u2014 \u804A\u51FA\u6587\u7AE0\u6765\u7684 MCP Apps \u5BA1\u9605\u754C\u9762
1332
1435
 
1333
1436
  \u4F5C\u4E3A MCP \u670D\u52A1\u5668\u8FD0\u884C\uFF08stdio\uFF09\u3002\u5BA1\u9605\u5DE5\u5177\u4F1A\u5728 agent \u5BF9\u8BDD\u91CC\u5D4C\u5165\u4EA4\u4E92\u754C\u9762\uFF08MCP Apps\uFF0Cui:// \u8D44\u6E90\uFF09\uFF0C