Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/components/generator/GeneratorApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ const QUALITY_LABEL_KEYS: Record<PromptHealthCheckId, keyof Dictionary['generato
metric: 'qualityMetric',
};

const QUALITY_HINT_KEYS: Record<PromptHealthCheckId, keyof Dictionary['generator']> = {
goal: 'qualityGoalHint',
features: 'qualityFeaturesHint',
io: 'qualityIoHint',
acceptance: 'qualityAcceptanceHint',
who: 'qualityWhoHint',
metric: 'qualityMetricHint',
};

export function GeneratorApp({ locale, dict }: Props) {
const [state, setState] = useState<FormState>(DEFAULT_FORM);
const [lang, setLang] = useState<PromptLang>(locale);
Expand Down Expand Up @@ -355,6 +364,7 @@ export function GeneratorApp({ locale, dict }: Props) {
{promptHealth.checks.map((check) => (
<span
key={check.id}
title={check.ok ? undefined : dict.generator[QUALITY_HINT_KEYS[check.id]]}
className={cn(
'rounded-full border px-2.5 py-1 text-[11px]',
check.ok
Expand All @@ -368,6 +378,25 @@ export function GeneratorApp({ locale, dict }: Props) {
</span>
))}
</div>
{!promptHealth.ready && (
<ul className="mt-3 space-y-1 text-[11.5px] leading-relaxed text-ink-mute">
{promptHealth.checks
.filter((check) => !check.ok)
.slice(0, 2)
.map((check) => (
<li key={`hint-${check.id}`} className="flex gap-1.5">
<span aria-hidden className="text-amber-600">→</span>
<span>
<span className="font-medium text-ink-soft">
{dict.generator[QUALITY_LABEL_KEYS[check.id]]}
</span>
{' · '}
{dict.generator[QUALITY_HINT_KEYS[check.id]]}
</span>
</li>
))}
</ul>
)}
</div>
<pre className="max-h-[520px] overflow-auto rounded-2xl border border-[color:var(--line)] bg-[#0F1115] p-4 text-[12.5px] leading-[1.75] text-[#E5E7EB]">
<code className="whitespace-pre-wrap break-words">{prompt}</code>
Expand Down
4 changes: 2 additions & 2 deletions src/data/cases/_more.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2249,7 +2249,7 @@ export const dataSkuProfitRadar: CaseBundle = {
communication: COMMUNICATION_ZH,
}, 'zh'),
en: composeCasePrompt({
role: caseRole('an e-commerce data analyst (tool must be usable by non-technical teammates)', 'en'),
role: caseRole('an e-commerce data analyst whose teammates across the business will use this tool — make it one-click and friendly', 'en'),
goal: 'Combine revenue, margin, ads, refunds, and stock turnover to find SKUs worth more resources.',
platform: '- Windows + macOS; Electron + React + TypeScript; local SQLite; SheetJS; ECharts',
features: `1. Import sales, cost, ad spend, refund, and stock sheets; merge by SKU.
Expand Down Expand Up @@ -2324,7 +2324,7 @@ export const productListingQualityChecker: CaseBundle = {
communication: COMMUNICATION_ZH,
}, 'zh'),
en: composeCasePrompt({
role: caseRole('merchandising/product teammates (must be usable by non-technical staff)', 'en'),
role: caseRole('merchandising/product teammates — the tool should feel natural in their hands, one-click with no setup', 'en'),
goal: 'Batch-check listing materials for completeness and consistency to reduce last-minute rework before launch.',
platform: '- Windows + macOS; Electron + React + TypeScript; SheetJS; local JSON rules',
features: `1. Import product sheet, image list, certificate file list.
Expand Down
8 changes: 2 additions & 6 deletions src/data/cases/finance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,13 @@ export const financeReconciliation: CaseBundle = {
platform: `- 平台:Windows 10 / 11 桌面应用
- 框架:Electron + React + TypeScript
- 表格处理:SheetJS
- 默认本地处理;联网传输须加密并告知用户
- 交付时打包成 Windows .exe 安装包`,
features: `1. 首页两个醒目按钮:「导入订单 Excel」「导入银行流水 Excel」,支持拖拽。
2. 导入后自动显示表头和前 5 行预览,让用户从下拉框里选「订单号」「金额」「交易时间」「备注」字段。记住选择下次默认回填。
3. 示例字段兼容:订单表常见列「订单号 / 实付金额 / 下单时间 / 店铺」;银行流水常见列「交易单号 / 收入金额 / 交易时间 / 摘要」。列名可有细微差异。
4. 「开始对账」按钮显示进度条。匹配逻辑:订单号去空格后作为主键;金额差 <= 0.01 元视为匹配;重复订单号要合并提示;退款 / 冲正流水单独标记,不直接当异常。
5. 结果页两栏:左边展示匹配数、差异数、匹配率、总金额;右边表格列出每一条差异,字段包括订单号、订单金额、银行金额、差额、可能原因(金额不一致 / 银行流水缺失 / 订单缺失 / 重复订单 / 疑似退款)。
6. 「导出差异明细到 Excel」按钮,默认文件名 "差异明细-YYYY-MM.xlsx"。
7. 数据默认本地处理,联网功能须告知用户并加密传输。`,
6. 「导出差异明细到 Excel」按钮,默认文件名 "差异明细-YYYY-MM.xlsx"。`,
sampleData: `sample-data/ 中放两个文件:
orders.xlsx — 列:订单号, 实付金额, 下单时间, 店铺(示例:DD202401001, 299.00, 2024-01-05, 旗舰店)
bank.xlsx — 列:交易单号, 收入金额, 交易时间, 摘要(示例:DD202401001, 299.00, 2024-01-06, 支付宝转入)
Expand Down Expand Up @@ -98,15 +96,13 @@ bank.xlsx — 列:交易单号, 收入金额, 交易时间, 摘要(示例:
platform: `- Platform: Windows 10 / 11 desktop app
- Framework: Electron + React + TypeScript
- Spreadsheet parsing: SheetJS
- Process locally by default; network calls require encryption and user consent
- Deliver a Windows .exe installer`,
features: `1. Home screen with two prominent buttons: "Import Orders Excel" and "Import Bank Excel". Drag-and-drop works.
2. After import, show headers and first 5 rows. Let the user pick Order ID / Amount / Transaction Time / Notes columns from dropdowns. Remember and pre-fill next time.
3. Example headers to support: orders may use Order ID / Paid Amount / Order Time / Store; bank statements may use Transaction ID / Income Amount / Transaction Time / Memo. Tolerate minor header wording differences.
4. "Reconcile" button triggers a progress bar. Rule: trim order IDs and use them as keys; difference <= 0.01 = match; duplicate order IDs are grouped and flagged; refunds / reversals are tagged separately, not treated as ordinary mismatches.
5. Results page has two panes. Left: matched count, mismatched count, match rate, total amount. Right: a mismatch table with order ID, order amount, bank amount, diff, reason (amounts differ / missing in bank / missing in orders / duplicate order / likely refund).
6. "Export mismatches to Excel" with default filename "diff-YYYY-MM.xlsx".
7. Process locally by default; network calls require encryption and user consent.`,
6. "Export mismatches to Excel" with default filename "diff-YYYY-MM.xlsx".`,
style: `- Minimal desktop-tool style: light background, clear sections, radius 8, moderate information density.
- Primary button: muted dark. Secondary: light gray.
- Follows Windows light/dark setting.
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/dictionaries/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ export const en = {
qualityMetric: 'Measurable gain',
qualityPass: 'OK',
qualityImprove: 'Add',
qualityGoalHint: 'In one or two sentences, say who it is for, what it solves, and the outcome.',
qualityFeaturesHint: 'One line each — be concrete: which file to drag in, which button to click, what comes out.',
qualityIoHint: 'Name the input and output, e.g. "Import Excel → export diff sheet".',
qualityAcceptanceHint: 'Spell out what counts as done in one line, e.g. "main flow runs, empty data does not crash, double-click to open".',
qualityWhoHint: 'Say who will use it in one phrase, e.g. "finance teammate / teacher / just me".',
qualityMetricHint: 'Give one measurable metric, e.g. "from 2 days to 1 hour" or "one click replaces 30 manual steps".',

quickTemplatesTitle: 'Start from a template',
quickTemplatesHint: 'One click fills the form. Tweak and go.',
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/dictionaries/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,12 @@ export const zh = {
qualityMetric: '量化收益',
qualityPass: '已填',
qualityImprove: '待补充',
qualityGoalHint: '一两句话说清楚:给谁用、解决什么、产出是什么。',
qualityFeaturesHint: '一行一条,具体到拖入哪个文件、点哪个按钮、产出什么。',
qualityIoHint: '写清楚输入和输出,例如"导入 Excel → 导出差异表"。',
qualityAcceptanceHint: '补一句怎么算做完,例如"主流程跑通、空数据不闪退、可双击打开"。',
qualityWhoHint: '写一下谁来用,例如"财务同事 / 老师 / 自己用都行"。',
qualityMetricHint: '给一个量化指标,例如"从 2 天压到 1 小时"或"一键替代手工 30 步"。',

quickTemplatesTitle: '不想从零开始?挑个模板',
quickTemplatesHint: '一键填好,改几个字就能用。',
Expand Down
23 changes: 22 additions & 1 deletion src/lib/generatorPromptHealth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ const WHO_WORDS = [
'部门',
'门店',
'小伙伴',
'客户',
'用户',
'学生',
'老师',
'team',
'finance',
'ops',
Expand All @@ -80,6 +84,11 @@ const WHO_WORDS = [
'colleague',
'department',
'store',
'teammate',
'customer',
'user',
'student',
'teacher',
] as const;

const METRIC_WORDS = [
Expand All @@ -96,6 +105,9 @@ const METRIC_WORDS = [
'节省',
'省下',
'快',
'批量',
'一键',
'成倍',
'hour',
'minute',
'second',
Expand All @@ -105,8 +117,17 @@ const METRIC_WORDS = [
'faster',
'times',
'save time',
'batch',
'one-click',
'instant',
'in one go',
] as const;

// Number + unit pattern: "2 天", "1 小时", "10 万行", "100k rows", "30%".
// A measurable goal usually has a quantified change; this catches more cases
// than the keyword list alone.
const METRIC_NUMBER_UNIT = /\d+\s*(?:%|天|时|分|秒|周|月|年|倍|万|千|百|个|条|行|次|页|kb|mb|gb|k|m|day|hour|min|sec|week|month|year|row|item|page|file)/i;

function includesAny(text: string, words: readonly string[]) {
const normalized = text.toLowerCase();
return words.some((word) => normalized.includes(word.toLowerCase()));
Expand Down Expand Up @@ -136,7 +157,7 @@ export function getPromptHealth(
},
{
id: 'metric',
ok: includesAny(requestText, METRIC_WORDS),
ok: includesAny(requestText, METRIC_WORDS) || METRIC_NUMBER_UNIT.test(requestText),
},
];
const passed = checks.filter((check) => check.ok).length;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/promptBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ export function buildRecoveryPrompt(state: FormState, lang: PromptLang): string
2. 最小 diff 修改;不删功能、不换技术栈来绕过
3. 重新运行:安装 / lint / 类型检查 / 构建 / 启动
4. 同一问题 3 次失败 → 降级边缘功能,先恢复主流程
5. 修完后用示例数据走完主流程,看到产物再说修好
5. 修完后用示例数据走完主流程,看到产物才算修好

遵守原提示词的所有安全底线和执行纪律。

Expand Down
6 changes: 3 additions & 3 deletions src/lib/promptModules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ describe('prompt module constants', () => {
expect(CONSTRAINTS_ZH).toContain('脱敏 sample-data');
expect(CONSTRAINTS_ZH).toContain('npm view');
expect(CONSTRAINTS_ZH).toContain('立即运行验证');
expect(CONSTRAINTS_ZH).toContain('另存为');
});

it('CONSTRAINTS_EN contains key rules', () => {
expect(CONSTRAINTS_EN).toContain('local');
expect(CONSTRAINTS_EN).toContain('anonymized sample-data');
expect(CONSTRAINTS_EN).toContain('npm view');
expect(CONSTRAINTS_EN).toContain('Verify each feature');
expect(CONSTRAINTS_EN).toContain('Save as');
});

it('DOD_ZH has checklist format', () => {
Expand Down Expand Up @@ -114,7 +116,7 @@ describe('prompt module constants', () => {

it('OPENING_BRIEF_EN primes Codex to greet the user in 3-8 numbered lines', () => {
expect(OPENING_BRIEF_EN).toContain('[Opening Brief]');
expect(OPENING_BRIEF_EN).toContain('3-8 lines');
expect(OPENING_BRIEF_EN).toContain('3-8 numbered lines');
expect(OPENING_BRIEF_EN).toContain('numbered');
expect(OPENING_BRIEF_EN).toContain("Don't wait");
expect(OPENING_BRIEF_EN).toContain("Don't promise a timeline");
Expand All @@ -125,7 +127,6 @@ describe('prompt module constants', () => {
expect(WARM_UX_ZH).toContain('Demo 模式');
expect(WARM_UX_ZH).toContain('用示例数据试一试');
expect(WARM_UX_ZH).toContain('业务语言');
expect(WARM_UX_ZH).toContain('另存为');
expect(WARM_UX_ZH).toContain('系统通知');
});

Expand Down Expand Up @@ -186,7 +187,6 @@ describe('prompt module constants', () => {
expect(WARM_UX_EN).toContain('demo mode');
expect(WARM_UX_EN).toContain('Try with sample data');
expect(WARM_UX_EN).toContain('business language');
expect(WARM_UX_EN).toContain('Save as');
expect(WARM_UX_EN).toContain('system notification');
});

Expand Down
44 changes: 10 additions & 34 deletions src/lib/promptModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export type ModuleTech = 'electron' | 'tauri' | 'pyqt' | 'auto';
export const SAFETY_RULES_ZH = `【安全底线】
- 默认本地处理;需要联网时须加密传输并明确告知用户。
- 不写死 API Key、绝对路径、个人邮箱或内网地址。
- 输出不覆盖原文件,冲突加时间后缀
- 写盘默认走"另存为";不覆盖原文件,冲突自动加时间后缀
- 不引入不存在的 npm 包;不确定时先查 npm view。
- 缺真实数据时先造脱敏 sample-data,不等用户提供文件才开工。`;

export const SAFETY_RULES_EN = `[Safety Rules]
- Process locally by default; network calls require encryption and explicit user consent.
- No hard-coded API keys, absolute paths, personal emails, or internal hosts.
- Never overwrite inputs; timestamp conflicts.
- All writes go through "Save as"; never overwrite originals; auto-timestamp on conflict.
- Do not invent npm packages; verify with npm view first.
- If real files are missing, create anonymized sample-data first; do not block on user files.`;

Expand All @@ -38,36 +38,14 @@ export const CONSTRAINTS_EN = `${SAFETY_RULES_EN}\n\n${QUALITY_RULES_EN}`;
// ─── Opening Brief (talk to the user before you build) ──────────

export const OPENING_BRIEF_ZH = `【开工前的开场白】
动手之前,先用 3-8 句告诉用户接下来会发生什么。3 句够就只用 3 句。

每句一行,带数字序号。短句。干净。有节奏。不用专业词。用「你」对话。讲画面、讲体感、讲结果——不讲实现。

下面是可选的素材清单,挑用得上的写,按这个顺序排——用不上的直接跳过,宁缺毋滥:
- 一句话点题:要做的是什么。
- 它替你解决什么。
- 关键的几步,一步一句。
- 用到了什么,一行带过。
- 打开后,你会看到什么。
- 你的文件存在哪,谁能看到。
- 下一步看什么。

不要承诺时间("X 分钟内可用"),因为不准。说完不等回话,按下面的流程立刻动手。`;
动手之前,先用 3-8 句告诉用户接下来会发生什么。3 句够就 3 句,每句一行、带数字序号。
讲画面、讲体感、讲结果——不讲实现。短句、不用专业词、用「你」对话。
不要承诺时间("X 分钟内可用"),因为不准。说完不等回话,按下面的【快速启动协议】立刻动手。`;

export const OPENING_BRIEF_EN = `[Opening Brief]
Before you build, use 3-8 lines to tell the user what's coming. If 3 lines do it, use 3.

One sentence per line, numbered. Short. Clean. Rhythmic. No jargon. Speak to "you". Picture, feel, result — not implementation.

Pick from this checklist — keep them in order, skip what doesn't fit. Better to leave one out than fill in fluff:
- What you'll build, in one line.
- The friction it removes.
- The key steps — one per line.
- The stack, in a line.
- What you'll see when it opens.
- Where your files live. Who sees them.
- What to look at next.

Don't promise a timeline ("ready in X minutes") — you can't know. Don't wait for a reply. Follow the flow below immediately.`;
Before you build, use 3-8 numbered lines to tell the user what's coming. If 3 lines do it, use 3 — one short sentence per line.
Picture, feel, result — not implementation. Plain words. Speak to "you".
Don't promise a timeline ("ready in X minutes") — you can't know. Don't wait for a reply. Follow the [Quick Start Protocol] below immediately.`;

// ─── Warm UX Contract (treat the user with care) ────────────────

Expand All @@ -76,9 +54,8 @@ export const WARM_UX_ZH = `【温暖体验契约】
- 首次启动 = Demo 模式:自动加载 sample-data/ 跑完主流程一次,让用户立刻看到结果界面,而不是空状态。
- 工作台顶部永远有「用示例数据试一试」按钮,任何时候都能一键演示。
- 按钮、提示、错误一律用业务语言。例:「找不到订单号这一列」,不是「Column "order_id" not found」。
- 任何写盘操作默认走「另存为」;从不覆盖原文件,冲突自动加时间后缀。
- 步骤 ≥3 的操作给"撤销"或"取消"出口;≥5 步的关键操作要二次确认。
- 大批量任务显示进度条 + 预估剩余时间,最长每 1 秒刷新一次
- 大批量任务显示进度条 + 预估剩余时间,每秒最多刷新一次
- 主流程一完成就在应用内给反馈;若窗口在后台,再发一次系统通知(Toast),点击直达结果。
- 失败时永远给出"下一步可以做什么"(重试 / 换文件 / 查看日志 / 复制错误),不要只留一行红色字。`;

Expand All @@ -87,7 +64,6 @@ What happens around the code matters more than the code itself. The finish shoul
- First launch = demo mode: auto-load sample-data/ and run the main flow once so the user sees a real result page, not an empty state.
- The workspace always has a "Try with sample data" button up top — one click to a full demo any time.
- Buttons, hints, and errors in business language. Example: "Can't find the Order ID column", not "Column 'order_id' not found".
- Any write goes through "Save as"; never overwrite originals; timestamp on conflict.
- Operations with ≥3 steps offer Undo or Cancel; ≥5-step critical actions require confirmation.
- Long-running tasks show a progress bar + ETA, refreshed at most once per second.
- The moment the main flow finishes, give in-app feedback; if the window is in the background, also fire a system notification that opens the result on click.
Expand All @@ -96,7 +72,7 @@ What happens around the code matters more than the code itself. The finish shoul
// ─── Success Picture (the moment of "wow") ──────────────────────

export const SUCCESS_PICTURE_ZH = `【完成态画面】
主流程结束的那一屏,是用户对这个工具的第一印象。把它当礼物来做。
主流程结束的那一屏,是用户对这个工具最持久的印象。把它当礼物来做。
- 大号数字 + 业务语言小结,30 字以内。例:「对账 482 单,差异 5 单。已存到 桌面/差异-2026-05.xlsx」。
- 关键发现用一行带颜色的 chip 摘要:「⚠ 3 单金额不一致 · ✦ 2 单疑似退款」。
- 三个动作按钮固定位置:「打开输出文件夹」「再做一次」「换一个文件」。
Expand Down
Loading
Loading