wtt-connect 0.2.2 → 0.2.3
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 +1 -0
- package/package.json +1 -1
- package/src/opendesign.js +89 -0
- package/src/runner.js +20 -2
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ Implemented production-oriented surfaces:
|
|
|
21
21
|
- `acp` for any Agent Client Protocol compatible agent
|
|
22
22
|
- `devin` through ACP (`devin acp`)
|
|
23
23
|
- Normalized tool/progress event publishing
|
|
24
|
+
- OpenDesign visual artifact generation for formula/principle/process/UI explanations, with a quality gate that checks for dense structure, SVG/canvas diagrams, animation, examples, and formula/metric explanation before upload
|
|
24
25
|
- TTS extension point:
|
|
25
26
|
- `none`
|
|
26
27
|
- `macos-say` using the macOS `say` command, emitted as WAV when upload is enabled
|
package/package.json
CHANGED
package/src/opendesign.js
CHANGED
|
@@ -54,9 +54,12 @@ export async function buildOpenDesignPrompt({ config, outputDir, userMessage, re
|
|
|
54
54
|
'- index.html must be complete and runnable in a sandbox iframe.',
|
|
55
55
|
'- Do not use external CDNs, remote images, external fonts, forms, cookies, localStorage, or network calls.',
|
|
56
56
|
'- Use inline SVG or local CSS/JS only.',
|
|
57
|
+
'- Treat this as a premium visual explainer, not a documentation page. The first viewport must look intentionally designed.',
|
|
57
58
|
'- For formula/principle explanations include: a main diagram, step animation, symbol/metric table, minimal concrete example, explanatory text beside visuals, and final compact summary.',
|
|
58
59
|
'- For UI/prototype requests include realistic fake data, hover states, and meaningful state transitions.',
|
|
59
60
|
'',
|
|
61
|
+
visualQualityContract(zh),
|
|
62
|
+
'',
|
|
60
63
|
`WTT topic_id: ${topicId || 'unknown'}`,
|
|
61
64
|
'',
|
|
62
65
|
zh ? '用户原始消息:' : 'Original user message:',
|
|
@@ -69,6 +72,69 @@ export async function buildOpenDesignPrompt({ config, outputDir, userMessage, re
|
|
|
69
72
|
].join('\n');
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
export function buildOpenDesignRepairPrompt({ outputDir, issues = [], locale = 'zh' }) {
|
|
76
|
+
const zh = locale === 'zh';
|
|
77
|
+
return [
|
|
78
|
+
'The previous OpenDesign artifact did not meet WTT visual quality requirements.',
|
|
79
|
+
`Rewrite the artifact in place in this directory: ${outputDir}`,
|
|
80
|
+
'Keep the same answer/topic meaning, but replace weak layout, weak styling, and missing motion.',
|
|
81
|
+
'',
|
|
82
|
+
'Detected issues:',
|
|
83
|
+
...issues.map((issue) => `- ${issue}`),
|
|
84
|
+
'',
|
|
85
|
+
visualQualityContract(zh),
|
|
86
|
+
'',
|
|
87
|
+
'Overwrite index.html, style.css, and main.js as needed. Reply with only: index.html',
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function evaluateOpenDesignArtifact(dir) {
|
|
92
|
+
const root = path.resolve(dir);
|
|
93
|
+
const indexPath = path.join(root, 'index.html');
|
|
94
|
+
const stylePath = path.join(root, 'style.css');
|
|
95
|
+
const scriptPath = path.join(root, 'main.js');
|
|
96
|
+
const issues = [];
|
|
97
|
+
let index = '';
|
|
98
|
+
let style = '';
|
|
99
|
+
let script = '';
|
|
100
|
+
try {
|
|
101
|
+
index = await fs.readFile(indexPath, 'utf8');
|
|
102
|
+
} catch {
|
|
103
|
+
return { ok: false, score: 0, issues: ['missing index.html'] };
|
|
104
|
+
}
|
|
105
|
+
try { style = await fs.readFile(stylePath, 'utf8'); } catch {}
|
|
106
|
+
try { script = await fs.readFile(scriptPath, 'utf8'); } catch {}
|
|
107
|
+
const combined = `${index}\n${style}\n${script}`;
|
|
108
|
+
let score = 0;
|
|
109
|
+
|
|
110
|
+
if (combined.length >= 7000) score += 1;
|
|
111
|
+
else issues.push('artifact is too sparse; expected a dense, high-fidelity explainer');
|
|
112
|
+
|
|
113
|
+
if (/<svg[\s>]/i.test(combined) || /<canvas[\s>]/i.test(combined)) score += 1;
|
|
114
|
+
else issues.push('missing SVG/canvas diagram for visual explanation');
|
|
115
|
+
|
|
116
|
+
if (/@keyframes|animation(?:-name)?:|transition:|stroke-dasharray|requestAnimationFrame/i.test(combined)) score += 1;
|
|
117
|
+
else issues.push('missing meaningful animation or motion system');
|
|
118
|
+
|
|
119
|
+
if (/:root\s*\{|--[a-z0-9-]+:/i.test(combined)) score += 1;
|
|
120
|
+
else issues.push('missing explicit design tokens / CSS variables');
|
|
121
|
+
|
|
122
|
+
const structureCount = (combined.match(/<(section|article|aside|figure|table)\b/gi) || []).length;
|
|
123
|
+
if (structureCount >= 5) score += 1;
|
|
124
|
+
else issues.push('layout lacks enough structured sections/cards/tables');
|
|
125
|
+
|
|
126
|
+
if (/示例|例子|example|case|sample/i.test(combined)) score += 1;
|
|
127
|
+
else issues.push('missing concrete example section');
|
|
128
|
+
|
|
129
|
+
if (/公式|formula|symbol|符号|metric|指标|table|thead|tbody/i.test(combined)) score += 1;
|
|
130
|
+
else issues.push('missing formula/symbol/metric explanation');
|
|
131
|
+
|
|
132
|
+
if (/data-step|step-|timeline|progress|播放|replay|next/i.test(combined)) score += 1;
|
|
133
|
+
else issues.push('missing step-by-step interaction/state');
|
|
134
|
+
|
|
135
|
+
return { ok: score >= 6, score, issues };
|
|
136
|
+
}
|
|
137
|
+
|
|
72
138
|
async function loadSkillBlocks(config, skillNames) {
|
|
73
139
|
const root = config.openDesignSkillsDir || '';
|
|
74
140
|
const blocks = [];
|
|
@@ -93,6 +159,29 @@ function fallbackSkillBlock() {
|
|
|
93
159
|
].join('\n');
|
|
94
160
|
}
|
|
95
161
|
|
|
162
|
+
function visualQualityContract(zh) {
|
|
163
|
+
return [
|
|
164
|
+
'WTT OpenDesign visual quality contract:',
|
|
165
|
+
'- Canvas: design for a 1440x900 whiteboard/preview area, responsive down to tablet. Use full-width composition, not a narrow article.',
|
|
166
|
+
'- Visual hierarchy: strong title block, one dominant SVG/canvas diagram, one compact example panel, one formula/symbol table, one step timeline, and a final takeaway strip.',
|
|
167
|
+
'- Default aesthetic when the user did not request a theme: light editorial research board, warm off-white paper, precise ink lines, subtle grid/noise, restrained color accents, refined spacing. It should feel closer to a polished team workspace/design note than a dark coding console.',
|
|
168
|
+
'- Aesthetic: choose a committed direction such as editorial lab notebook, precision engineering console, warm classroom blackboard, industrial systems map, or high-contrast research poster. Avoid generic SaaS cards.',
|
|
169
|
+
'- Typography: use distinctive CSS font stacks from local system fonts only, for example Georgia/Times editorial serif plus ui-monospace accents, or Avenir/Helvetica Neue with a mono data layer. Do not use plain Inter/Roboto/Arial defaults.',
|
|
170
|
+
'- Color: define CSS variables in :root. Use a restrained but intentional palette with textured background, grid/noise/paper effect, or layered panels. Avoid flat dark gray, neon cyan buttons, purple gradients, and default dark-mode dashboards unless the content explicitly requires them.',
|
|
171
|
+
'- Polish: use fine borders, soft shadows, intentional whitespace, aligned baselines, and varied panel sizes. Do not make every block the same rounded rectangle.',
|
|
172
|
+
'- SVG: include at least one large inline SVG with labeled nodes, arrows, and a visible animated path. Arrow animation must be obvious: stroke-dasharray/stroke-dashoffset or moving marker/dot.',
|
|
173
|
+
'- Motion: include 2-4 purposeful animations: entry reveal, arrow/path draw, active step highlight, formula/example transition. Respect prefers-reduced-motion.',
|
|
174
|
+
'- Explanation: every diagram node must have adjacent text that explains why it matters. Do not rely on labels alone.',
|
|
175
|
+
'- Example: include a minimal concrete example with inputs, intermediate transformation, and output/result.',
|
|
176
|
+
'- Density: avoid empty hero-only pages. The first viewport should already contain the main diagram and explanatory panels.',
|
|
177
|
+
'- Controls: provide Replay / Step controls when animation or process is present.',
|
|
178
|
+
'- Finish: no placeholder lorem ipsum, no “template” language, no meta comments about being generated.',
|
|
179
|
+
zh
|
|
180
|
+
? '- Chinese copy must be concise, concrete, and tied to the user/agent answer. Do not use “苏格拉底/架构概念/问题分解/完整答案” as HTML section names unless the user explicitly asks.'
|
|
181
|
+
: '- Copy must be concise, concrete, and tied to the user/agent answer. Do not use generic section names unless the user explicitly asks.',
|
|
182
|
+
].join('\n');
|
|
183
|
+
}
|
|
184
|
+
|
|
96
185
|
function parseMetadata(metadata) {
|
|
97
186
|
if (!metadata) return null;
|
|
98
187
|
if (typeof metadata === 'object') return metadata;
|
package/src/runner.js
CHANGED
|
@@ -14,7 +14,7 @@ import { log } from './logger.js';
|
|
|
14
14
|
import { buildRuntimeInfo } from './runtime-info.js';
|
|
15
15
|
import { runShellCommand } from './shell-runner.js';
|
|
16
16
|
import { TerminalSessionManager } from './terminal-session.js';
|
|
17
|
-
import { buildOpenDesignPrompt, chooseOpenDesignSkills, prepareOpenDesignDir, shouldRenderOpenDesignArtifact } from './opendesign.js';
|
|
17
|
+
import { buildOpenDesignPrompt, buildOpenDesignRepairPrompt, chooseOpenDesignSkills, evaluateOpenDesignArtifact, prepareOpenDesignDir, shouldRenderOpenDesignArtifact } from './opendesign.js';
|
|
18
18
|
|
|
19
19
|
const TERMINAL_STATUSES = new Set(['review', 'done', 'approved', 'cancelled']);
|
|
20
20
|
|
|
@@ -288,13 +288,14 @@ export class Runner {
|
|
|
288
288
|
});
|
|
289
289
|
const outputDir = await prepareOpenDesignDir(this.config, topicId);
|
|
290
290
|
const skills = chooseOpenDesignSkills(message, reply);
|
|
291
|
+
const locale = inferLocale(`${message.content || ''}\n${reply || ''}`);
|
|
291
292
|
const prompt = await buildOpenDesignPrompt({
|
|
292
293
|
config: this.config,
|
|
293
294
|
outputDir,
|
|
294
295
|
userMessage: message.content || '',
|
|
295
296
|
reply,
|
|
296
297
|
topicId,
|
|
297
|
-
locale
|
|
298
|
+
locale,
|
|
298
299
|
skills,
|
|
299
300
|
});
|
|
300
301
|
await adapter.run(prompt, {
|
|
@@ -302,6 +303,23 @@ export class Runner {
|
|
|
302
303
|
topicId,
|
|
303
304
|
onProgress: (event) => this.maybePublishProgress(topicId, event, adapter.name),
|
|
304
305
|
});
|
|
306
|
+
let quality = await evaluateOpenDesignArtifact(outputDir);
|
|
307
|
+
if (!quality.ok) {
|
|
308
|
+
log('warn', 'opendesign artifact quality check failed; requesting rewrite', { topicId, score: quality.score, issues: quality.issues.join('; ') });
|
|
309
|
+
await this.wtt.typing(topicId, 'start', {
|
|
310
|
+
statusText: 'OpenDesign 正在重写低质量 artifact',
|
|
311
|
+
statusKind: 'opendesign_repair',
|
|
312
|
+
adapter: adapter.name,
|
|
313
|
+
ttlMs: 45000,
|
|
314
|
+
});
|
|
315
|
+
await adapter.run(buildOpenDesignRepairPrompt({ outputDir, issues: quality.issues, locale }), {
|
|
316
|
+
sessionKey: `wtt:opendesign:${topicId}`,
|
|
317
|
+
topicId,
|
|
318
|
+
onProgress: (event) => this.maybePublishProgress(topicId, event, adapter.name),
|
|
319
|
+
});
|
|
320
|
+
quality = await evaluateOpenDesignArtifact(outputDir);
|
|
321
|
+
log(quality.ok ? 'info' : 'warn', 'opendesign artifact quality check after rewrite', { topicId, score: quality.score, issues: quality.issues.join('; ') });
|
|
322
|
+
}
|
|
305
323
|
const artifact = await this.artifacts.uploadDirectory(outputDir, {
|
|
306
324
|
source: messageTopicType(message) || 'feed',
|
|
307
325
|
sourceId: topicId,
|