Skip to content

feat: 新增 AI 驱动句子云海报生成器 - #1

Merged
Learnmore-smart merged 1 commit into
mainfrom
trae/solo-agent-33unb2
Jun 8, 2026
Merged

feat: 新增 AI 驱动句子云海报生成器#1
Learnmore-smart merged 1 commit into
mainfrom
trae/solo-agent-33unb2

Conversation

@Learnmore-smart

Copy link
Copy Markdown
Owner

🎯 Changes

1. 新增 AI 驱动句子云海报生成器

  • 新增 index.html 文件,包含一个完整的单文件 HTML 应用,实现 AI 驱动的句子云海报生成功能。
  • 内置 15 条种子 Quote 数组,提供基础内容。
  • 集成字体与配色基础样式,确保视觉一致性。
  • 实现离屏 Canvas 文本测量,用于精确布局。
  • 采用阿基米德螺旋线与 AABB 碰撞检测算法进行文本布局,优化视觉效果。
  • 集成 OpenRouter google/gemini-flash-1.5 进行 AI 权重请求,并提供本地启发式回退机制,增强鲁棒性。
  • 新增控制面板,支持纸张尺寸、方向、作者开关等配置,并提供打印按钮。
  • 实现纸张 aspect-ratio 适配与等比居中缩放功能,确保不同尺寸下的显示效果。
  • 动态注入 @page 规则,优化浏览器原生打印管线。
  • 新增 Hover 聚焦(高亮 + 其他变暗模糊)与作者淡入交互,提升用户体验。
  • 实现 Loading 态展示与 resize 重新计算逻辑,优化用户感知。

2. 新增配套文档

  • 新增 spec.md 规格文档,详细定义了 AI 权重分析、Canvas 多行文本测量、阿基米德螺旋线布局、画板比例与等比缩放、控制面板与打印、Hover 聚焦交互、作者显示策略七大核心能力的需求与场景。
  • 新增 tasks.md 任务清单,按 8 个 Task 拆解了 HTML 骨架、文本测量、螺旋布局、缩放、控制面板/打印、交互、AI 容错与集成验证的实现步骤,并标注了任务依赖关系。
  • 新增 checklist.md 验收清单,逐项核对单文件可运行性、API Key 占位、测量函数签名、权重排序、螺旋线步进、AABB 零重叠、纸张比例、打印样式、Loading 态等交付要点。

💡 Technical Highlights

  • 零依赖单文件应用: 整个应用封装在一个 HTML 文件中,易于部署和分享。
  • AI 权重分析: 利用 OpenRouter AI 服务对句子进行权重分析,实现智能排版。
  • Canvas 文本测量: 精确测量多行文本尺寸,为阿基米德螺旋线布局提供数据支持。
  • 阿基米德螺旋线布局: 结合 AABB 碰撞检测,实现美观且无重叠的文本排版。
  • 浏览器原生打印管线: 动态注入 @page 规则,充分利用浏览器打印能力,提供高质量打印输出。
  • 交互式用户体验: 通过 Hover 聚焦和作者淡入等交互,提升用户对生成海报的探索性。

Co-authored-by: traeagent <traeagent@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 8, 2026 07:14

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44c12622f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread index.html
page-break-inside: avoid;
}
.paper-canvas::before { display: none !important; }
.paper-content { transform: var(--print-transform, none) !important; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Don't override the computed print scale

When the user clicks Print Poster, applyPrintScale() writes a scaled inline transform, but this @media print rule is !important and forces .paper-content back to --print-transform, which is only the unscaled translate set by computeContentTransform(). On A4 portrait, for example, the layout is 1131×1600 design pixels while the printed page is about 794px wide, so the poster content is clipped instead of being scaled to the physical page.

Useful? React with 👍 / 👎.

Comment thread index.html
Comment on lines +1067 to +1069
window.addEventListener('resize', () => {
fitPaperToStage();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recompute the content transform after resizing

When the viewport changes size after the poster has rendered, this handler only resizes the paper element; the existing .paper-content transform still contains the old cssScale from the previous computeContentTransform() call. Narrowing the window can therefore leave quotes too large and clipped by the paper, while widening leaves the poster unnecessarily small until another control triggers a full re-render.

Useful? React with 👍 / 👎.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an AI-driven Interactive Quote Cloud & Poster Generator as a single-file HTML application, complete with specifications, checklists, and task documentation. The application features OpenRouter API integration for quote weight analysis, an Archimedean spiral layout algorithm with AABB collision detection, responsive canvas scaling, and print pipeline support. The review feedback highlights several key improvements: resolving a print scaling issue where the scale factor was overridden by CSS by dynamically updating the custom property, enhancing layout accuracy by passing weight-specific font weights and styles to the text measurement function instead of using a hardcoded value, and ensuring proper content scaling on window resize by invoking the transform computation in the resize handler.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread index.html
Comment on lines +1057 to +1059
$content.style.transformOrigin = '0 0';
$content.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

问题分析:在 @media print 样式中,.paper-contenttransform 被设置为了 var(--print-transform, none) !important。由于 !important 的存在,JS 中通过 $content.style.transform 设置的缩放(scale(${scale}))在打印时会被完全覆盖。而 --print-transform 变量仅在 computeContentTransform 中被设置为不带缩放的 translate,这导致打印预览和实际打印时海报内容无法正确缩放以适应纸张大小,从而产生裁剪或溢出。\n\n解决方案:在 applyPrintScale 中,同步更新 $paper 上的 --print-transform 自定义属性,使其包含计算后的 scale,从而确保打印管线能够正确应用缩放。

Suggested change
$content.style.transformOrigin = '0 0';
$content.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
}
$content.style.transformOrigin = '0 0';
$content.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
$paper.style.setProperty('--print-transform', `translate(${tx}px, ${ty}px) scale(${scale})`);
}

Comment thread index.html
Comment on lines +500 to +510
function setMeasureFont(fontSize, fontWeight) {
_mctx.font = `${fontWeight} ${fontSize}px ${FONT_STACK}`;
}

/**
* Word-wrap a string into lines that fit within maxWidth, measured with
* context.measureText(). Supports CJK (per-character fallback) and Latin
* (whitespace tokenization). Returns {lines, width, height}.
*/
function measureMultilineText(text, fontSize, fontWeight, maxWidth) {
setMeasureFont(fontSize, fontWeight);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

问题分析:在 spiralLayout 中调用 measureMultilineText 时,字体粗细(font weight)被硬编码为 700,且未考虑字体样式(font style)。然而,CSS 中不同权重的 quote 实际渲染样式不同:w3700w2500w1400 且为斜体(italic)。这种测量与渲染样式的不一致会导致 Canvas 文本测量不准确,从而可能引起布局重叠或不必要的空白。\n\n解决方案:建议在 paramsForWeight 中返回对应的 fontWeightfontStyle,并在测量时动态传入,以确保测量与渲染的字体指标完全一致。

Suggested change
function setMeasureFont(fontSize, fontWeight) {
_mctx.font = `${fontWeight} ${fontSize}px ${FONT_STACK}`;
}
/**
* Word-wrap a string into lines that fit within maxWidth, measured with
* context.measureText(). Supports CJK (per-character fallback) and Latin
* (whitespace tokenization). Returns {lines, width, height}.
*/
function measureMultilineText(text, fontSize, fontWeight, maxWidth) {
setMeasureFont(fontSize, fontWeight);
function setMeasureFont(fontSize, fontWeight, fontStyle = 'normal') {
_mctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${FONT_STACK}`;
}
/**
* Word-wrap a string into lines that fit within maxWidth, measured with
* context.measureText(). Supports CJK (per-character fallback) and Latin
* (whitespace tokenization). Returns {lines, width, height}.
*/
function measureMultilineText(text, fontSize, fontWeight, maxWidth, fontStyle = 'normal') {
setMeasureFont(fontSize, fontWeight, fontStyle);

Comment thread index.html
Comment on lines +674 to +681
function paramsForWeight(weight) {
// Returns per-weight type/layout params
switch (weight) {
case 3: return { fontSize: 64, lineHeight: 64*1.18, maxWidth: 760, color: 'var(--w3-color)', className: 'w3' };
case 2: return { fontSize: 40, lineHeight: 40*1.18, maxWidth: 560, color: 'var(--w2-color)', className: 'w2' };
default:return { fontSize: 24, lineHeight: 24*1.18, maxWidth: 360, color: 'var(--w1-color)', className: 'w1' };
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

问题分析:为了配合 Canvas 文本测量的精确度提升,我们需要在 paramsForWeight 中定义每个权重对应的 fontWeightfontStyle,以便在测量和渲染时使用完全一致的字体属性。

Suggested change
function paramsForWeight(weight) {
// Returns per-weight type/layout params
switch (weight) {
case 3: return { fontSize: 64, lineHeight: 64*1.18, maxWidth: 760, color: 'var(--w3-color)', className: 'w3' };
case 2: return { fontSize: 40, lineHeight: 40*1.18, maxWidth: 560, color: 'var(--w2-color)', className: 'w2' };
default:return { fontSize: 24, lineHeight: 24*1.18, maxWidth: 360, color: 'var(--w1-color)', className: 'w1' };
}
}
function paramsForWeight(weight) {
// Returns per-weight type/layout params
switch (weight) {
case 3: return { fontSize: 64, lineHeight: 64*1.18, maxWidth: 760, color: 'var(--w3-color)', className: 'w3', fontWeight: 700, fontStyle: 'normal' };
case 2: return { fontSize: 40, lineHeight: 40*1.18, maxWidth: 560, color: 'var(--w2-color)', className: 'w2', fontWeight: 500, fontStyle: 'normal' };
default:return { fontSize: 24, lineHeight: 24*1.18, maxWidth: 360, color: 'var(--w1-color)', className: 'w1', fontWeight: 400, fontStyle: 'italic' };
}
}

Comment thread index.html
for (let qi = 0; qi < sorted.length; qi++) {
const q = sorted[qi];
const p = paramsForWeight(q.weight);
const m = measureMultilineText(q.text, p.fontSize, 700, p.maxWidth);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

问题分析:在排版计算时,应使用当前 quote 权重对应的真实 fontWeightfontStyle 进行测量,而不是硬编码的 700,以确保排版碰撞检测的包围盒与最终渲染效果完全匹配。

Suggested change
const m = measureMultilineText(q.text, p.fontSize, 700, p.maxWidth);
const m = measureMultilineText(q.text, p.fontSize, p.fontWeight, p.maxWidth, p.fontStyle);

Comment thread index.html
Comment on lines +1067 to +1069
window.addEventListener('resize', () => {
fitPaperToStage();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

问题分析:当浏览器窗口大小改变时,resize 事件监听器仅调用了 fitPaperToStage() 来调整纸张容器的大小,但没有重新计算并应用内容的缩放和居中偏移(即未调用 computeContentTransform())。这会导致在调整窗口大小后,句子云内容无法自适应缩放,出现内容过大溢出或过小留白的问题。\n\n解决方案:在 resize 事件监听器中,在调用 fitPaperToStage() 后紧接着调用 computeContentTransform(),以确保内容缩放实时更新。

Suggested change
window.addEventListener('resize', () => {
fitPaperToStage();
});
window.addEventListener('resize', () => {
fitPaperToStage();
computeContentTransform();
});

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a zero-dependency single-file web app (index.html) that generates an AI-weighted quote-cloud poster with Archimedean-spiral layout, interactive focus-on-hover, paper sizing/orientation controls, and a print pipeline. It also adds accompanying spec/task/checklist documentation for the feature.

Changes:

  • Added a standalone HTML/CSS/JS poster generator implementing canvas-based text measurement, spiral placement with AABB collision detection, and interactive rendering.
  • Integrated OpenRouter (google/gemini-flash-1.5) weight analysis with a deterministic local heuristic fallback.
  • Added feature specifications, task breakdown, and acceptance checklist under .trae/specs/....

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
index.html New single-file poster generator app (layout, AI weighting, controls, print, interactions).
.trae/specs/create-quote-cloud-poster-generator/spec.md New spec describing functional/UX requirements and scenarios.
.trae/specs/create-quote-cloud-poster-generator/tasks.md New implementation task plan and dependencies.
.trae/specs/create-quote-cloud-poster-generator/checklist.md New acceptance checklist for feature verification.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread index.html
Comment on lines +674 to +681
function paramsForWeight(weight) {
// Returns per-weight type/layout params
switch (weight) {
case 3: return { fontSize: 64, lineHeight: 64*1.18, maxWidth: 760, color: 'var(--w3-color)', className: 'w3' };
case 2: return { fontSize: 40, lineHeight: 40*1.18, maxWidth: 560, color: 'var(--w2-color)', className: 'w2' };
default:return { fontSize: 24, lineHeight: 24*1.18, maxWidth: 360, color: 'var(--w1-color)', className: 'w1' };
}
}
Comment thread index.html
for (let qi = 0; qi < sorted.length; qi++) {
const q = sorted[qi];
const p = paramsForWeight(q.weight);
const m = measureMultilineText(q.text, p.fontSize, 700, p.maxWidth);
Comment thread index.html
*/
function measureMultilineText(text, fontSize, fontWeight, maxWidth) {
setMeasureFont(fontSize, fontWeight);
const lineHeight = Math.round(fontSize * 1.18);
Comment thread index.html
Comment on lines +1067 to +1069
window.addEventListener('resize', () => {
fitPaperToStage();
});
Comment thread index.html
display: inline-block;
width: 36px; height: 20px;
}
.switch input { display: none; }
Comment on lines +37 to +41
### Requirement: Canvas 多行文本测量
系统 SHALL 在内存中创建隐藏的离屏 `<canvas>` 元素,使用 `context.measureText()` 测量文本尺寸。
系统 MUST 提供 `measureMultilineText(text, fontSize, fontWeight, maxWidth)` 函数,返回 `[width, height]`。
系统 MUST 在 Canvas 虚拟环境中模拟 word-wrapping(按词或字符切分),按 `lineHeight = fontSize * 1.2` 累计高度。
不同 `weight` 应对应不同 `fontSize`(weight 3 最大)和不同 `maxWidth`(大字号对应更宽的 maxWidth)。
Comment on lines +8 to +10
- [x] Task 2: 实现 Canvas 多行文本测量
- [x] SubTask 2.1: 创建离屏 `<canvas>` 实例,封装 `measureMultilineText(text, fontSize, fontWeight, maxWidth)` 返回 `[width, height]`
- [x] SubTask 2.2: 按 `weight` 映射 fontSize 与 maxWidth(weight 3 → 最大字号 + 较宽 maxWidth)
Comment on lines +6 to +8
- [x] 使用离屏 `<canvas>` + `context.measureText()` 测量文本,不使用 `offsetWidth/offsetHeight`
- [x] `measureMultilineText(text, fontSize, fontWeight, maxWidth)` 函数签名与返回值正确
- [x] 不同 weight 映射不同 fontSize 与 maxWidth(weight 3 最大 + 较宽)
@Learnmore-smart
Learnmore-smart merged commit 1ee880b into main Jun 8, 2026
1 check passed
@Learnmore-smart
Learnmore-smart deleted the trae/solo-agent-33unb2 branch June 9, 2026 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants