ai smart auto-documentation and text rewrite

- Store captureMetadata (OCR text, window/app/element info) with each
  step at capture time so AI always has the original rich context.
- Add buildCaptureContext() to TextIntelService; capture.js uses it
  instead of buildCaptureTitle() so both title and metadata come from
  one pass.
- generateStepPatch() prefers stored captureMetadata over re-running
  OCR, giving the AI the best possible context when the user clicks
  an AI button later.
- Add autoDoc setting: when enabled every capture (shoot, region, and
  session hotkey/click) is automatically documented by AI. Manual
  captures await AI before returning; session captures fire-and-forget
  and push a step:updated event so the renderer reloads seamlessly.
- Add ai:rewriteText IPC and rewriteText() method for plain-text
  polishing via a separate callOllamaText() that skips JSON mode.
- Add "AI Rewrite" section in the editor right panel: textarea + AI
  button that rewrites whatever the user types in place.
- Improve buildAiPrompt() rules: action-focused title instructions,
  explicit anti-junk rules (no "Capture the screen / OCR" blocks),
  and a context-quality gate that suppresses blocks when context is
  thin.
- Add autoDoc checkbox to AI settings dialog.
- Renderer handles step:updated to reload the selected step after
  background auto-doc finishes.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
2026-06-24 09:17:31 -05:00
co-authored by Claude Sonnet 4.6
parent 375c14b028
commit d244626583
10 changed files with 255 additions and 32 deletions
+48
View File
@@ -167,6 +167,7 @@ class GuideEditor {
const buttons = [
this.dom?.titleAiBtn,
this.dom?.descAiBtn,
this.dom?.rewriteAiBtn,
...(this.dom?.blocksList ? [...this.dom.blocksList.querySelectorAll('button[data-ai-action]')] : []),
].filter(Boolean);
for (const button of buttons) {
@@ -222,6 +223,36 @@ class GuideEditor {
return this.runAiGeneration('all', { button });
}
async runTextRewrite(button = null) {
if (!this.isAiEnabled()) {
this.onToast('Enable AI in Settings first.', { error: true });
return;
}
const text = this.dom?.rewriteInput?.value?.trim();
if (!text) {
this.onToast('Type some text to rewrite first.', { error: true });
return;
}
if (button) setButtonLoading(button, true, 'Rewriting…');
try {
const result = await api.ai.rewriteText({
text,
guideTitle: this.guide?.title || '',
stepTitle: this.currentStep?.title || '',
});
if (!result || !result.ok) {
this.onToast(result?.reason || 'Rewrite failed.', { error: true });
return;
}
if (this.dom?.rewriteInput) this.dom.rewriteInput.value = result.text;
this.onToast('Text rewritten.');
} catch (err) {
this.onToast(err.message || 'Rewrite failed.', { error: true });
} finally {
if (button) setButtonLoading(button, false);
}
}
async generateBlockWithAi(kind, block, button = null) {
if (!block) return null;
return this.runAiGeneration('block', { blockId: block.id, button });
@@ -411,6 +442,21 @@ class GuideEditor {
this.dom.addTableBlockBtn = el('button', { type: 'button' }, '+ Table'),
),
),
el('section', {},
el('div.row', { style: { justifyContent: 'space-between', alignItems: 'center' } },
el('h3', { style: { margin: 0 } }, 'AI Rewrite'),
this.dom.rewriteAiBtn = el('button.ai', {
type: 'button',
title: 'Rewrite the text below with AI',
dataset: { aiAction: 'rewrite', aiTitle: 'Rewrite with AI' },
}, 'Rewrite'),
),
this.dom.rewriteInput = el('textarea', {
rows: 3,
placeholder: 'Type something to improve…',
style: { width: '100%', resize: 'vertical' },
}),
),
el('section', {},
el('h3', {}, 'Guide'),
this.dom.guideSummary = el('div.muted', {}),
@@ -583,6 +629,8 @@ class GuideEditor {
});
this.dom.descAiBtn.addEventListener('click', () => this.generateDescriptionWithAi(this.dom.descAiBtn));
this.dom.rewriteAiBtn.addEventListener('click', () => this.runTextRewrite(this.dom.rewriteAiBtn));
this.dom.descEditor.addEventListener('paste', (e) => {
// Keep pasted text simple; backend sanitization will handle the rest.
e.preventDefault();