fix ai title generation and remove separate rewrite section
- Remove the standalone "AI Rewrite" section from the editor panel.
The existing title and description AI buttons already flush the step
first, so they rewrite whatever the user has typed in those fields.
- Never pass a generic fallback title ("Screen capture", "Window
capture", "Region capture", "Capture") as the AI title candidate or
as step content. It is now treated as "(not set — generate a
specific action title)" so the AI always produces something real.
- hasRichCaptureContext now counts any non-trivial app name or window
title as sufficient context, instead of requiring non-browser noise.
- Prompt rules updated: "NEVER output Screen/Window/Region capture",
separate paths for improving a user draft vs generating from context,
and clearer guidance when context is limited (use app/window name).
- isPlaceholderTitle helper guards summarizeStepForAi so a
default-titled step presents itself as empty to the AI.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
@@ -167,7 +167,6 @@ 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) {
|
||||
@@ -223,36 +222,6 @@ 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 });
|
||||
@@ -442,21 +411,6 @@ 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', {}),
|
||||
@@ -629,8 +583,6 @@ 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();
|
||||
|
||||
+17
-12
@@ -5,6 +5,7 @@ const path = require('node:path');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
|
||||
const {
|
||||
DEFAULT_CAPTURE_TITLES,
|
||||
buildCaptureTitle,
|
||||
normalizeOllamaHost,
|
||||
normalizeAiPatch,
|
||||
@@ -14,6 +15,8 @@ const {
|
||||
normalizeWhitespace,
|
||||
} = require('../core/text-intel');
|
||||
|
||||
const DEFAULT_TITLE_VALUES = new Set(Object.values(DEFAULT_CAPTURE_TITLES).concat(['Capture']));
|
||||
|
||||
const OCR_CROP = {
|
||||
width: 420,
|
||||
height: 220,
|
||||
@@ -430,18 +433,20 @@ public static class Win32 {
|
||||
// Use stored capture metadata when available (best context, from capture time).
|
||||
// Fall back to re-running OCR on the stored image only when metadata is absent.
|
||||
if (step.captureMetadata) {
|
||||
const rawCandidate = buildCaptureTitle({
|
||||
mode: step.captureMetadata.mode || 'fullscreen',
|
||||
metadata: {
|
||||
windowTitle: step.captureMetadata.windowTitle,
|
||||
appName: step.captureMetadata.appName,
|
||||
elementLabel: step.captureMetadata.elementLabel,
|
||||
elementRole: step.captureMetadata.elementRole,
|
||||
},
|
||||
ocrText: step.captureMetadata.ocrText,
|
||||
});
|
||||
captureContext = {
|
||||
...step.captureMetadata,
|
||||
titleCandidate: buildCaptureTitle({
|
||||
mode: step.captureMetadata.mode || 'fullscreen',
|
||||
metadata: {
|
||||
windowTitle: step.captureMetadata.windowTitle,
|
||||
appName: step.captureMetadata.appName,
|
||||
elementLabel: step.captureMetadata.elementLabel,
|
||||
elementRole: step.captureMetadata.elementRole,
|
||||
},
|
||||
ocrText: step.captureMetadata.ocrText,
|
||||
}),
|
||||
// Don't suggest a generic fallback title — leave it blank so AI generates from context.
|
||||
titleCandidate: DEFAULT_TITLE_VALUES.has(rawCandidate) ? '' : rawCandidate,
|
||||
};
|
||||
} else if (step.image) {
|
||||
const imagePath = this.store.stepImagePath(guideId, stepId, 'working') || this.store.stepImagePath(guideId, stepId, 'original');
|
||||
@@ -454,7 +459,7 @@ public static class Win32 {
|
||||
this.collectForegroundWindowContext(),
|
||||
this.ocrAroundClick({ image, size: image.getSize(), display: { bounds: { x: 0, y: 0, width: image.getSize().width, height: image.getSize().height } } }, clickPoint),
|
||||
]);
|
||||
const titleCandidate = buildCaptureTitle({
|
||||
const rawCandidate2 = buildCaptureTitle({
|
||||
mode: step.kind === 'image' ? 'fullscreen' : 'window',
|
||||
metadata,
|
||||
ocrText: ocr.text,
|
||||
@@ -462,7 +467,7 @@ public static class Win32 {
|
||||
captureContext = {
|
||||
...metadata,
|
||||
ocrText: ocr.text,
|
||||
titleCandidate,
|
||||
titleCandidate: DEFAULT_TITLE_VALUES.has(rawCandidate2) ? '' : rawCandidate2,
|
||||
mode: step.kind === 'image' ? 'fullscreen' : 'content',
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user