feat: Implement Prompt Builder functionality in Popup and Options
Some checks failed
CI — Test & Build / Test & Build (push) Failing after 39s

- Added a new "Prompt Builder" tab in the Popup for generating AI prompts with customizable parameters.
- Introduced new state variables for managing prompt styles, personas, formats, and models.
- Enhanced the Options page to fetch and display models based on the provided API key.
- Updated the actions and types to include the new 'prompt' action and its associated parameters.
- Implemented migration logic for legacy plaintext API keys to encrypted storage.
- Updated the getSystemPrompt function to incorporate prompt parameters for better instruction generation.
- Added tests for the new functionality, including context menu entries and prompt generation logic.
This commit is contained in:
john kevin asprec
2026-07-15 15:27:41 +08:00
parent 0fef9848cb
commit acea99d7ad
40 changed files with 1971 additions and 177 deletions

View File

@@ -4,7 +4,7 @@
// 'fix' is the user-facing id emitted by the context menu and popup; the
// background normalizes it to the 'grammar' prompt.
export const ACTIONS = ['fix', 'rephrase', 'shorten', 'expand', 'explain'] as const;
export const ACTIONS = ['fix', 'rephrase', 'shorten', 'expand', 'explain', 'prompt'] as const;
export type ActionId = (typeof ACTIONS)[number];
export const ACTION_LABELS: Record<ActionId, string> = {
@@ -13,6 +13,7 @@ export const ACTION_LABELS: Record<ActionId, string> = {
shorten: 'Shorten',
expand: 'Expand',
explain: 'Explain',
prompt: 'Make Prompt',
};
// Full list, including the 'Default' pseudo-style (= no style modifier).
@@ -22,6 +23,37 @@ export type WritingStyle = (typeof WRITING_STYLES)[number];
// Context menus omit 'Default' — the parent action item already covers it.
export const CONTEXT_MENU_STYLES = WRITING_STYLES.filter((s) => s !== 'Default');
// ─── Prompt Builder parameters (the 'prompt' action) ─────────────────────────
// 'Auto' always means "let the prompt engineer decide from the input".
export const PROMPT_STYLES = ['Auto', 'Instructional', 'Role-play', 'Step-by-step', 'Few-shot', 'Structured'] as const;
export type PromptStyle = (typeof PROMPT_STYLES)[number];
// 'Custom…' switches the popup to a free-text persona field.
export const PROMPT_PERSONAS = [
'Auto',
'None',
'Expert Developer',
'Copywriter',
'Teacher',
'Data Analyst',
'Business Consultant',
'Researcher',
'Custom…',
] as const;
export type PromptPersona = (typeof PROMPT_PERSONAS)[number];
export const PROMPT_FORMATS = ['Auto', 'Plain text', 'Markdown', 'Bulleted list', 'Numbered steps', 'JSON', 'Table'] as const;
export type PromptFormat = (typeof PROMPT_FORMATS)[number];
// 'Custom…' means "use the free-text persona"; an empty custom text falls
// back to Auto. Shared by the popup and the background (which resolves saved
// Prompt Builder settings for toolbar/context-menu prompt requests).
export function resolvePromptPersona(persona?: string, customPersona?: string): string | undefined {
if (persona === 'Custom…') return customPersona?.trim() || 'Auto';
return persona;
}
// Minimum selection length (chars, after trim) before the floating toolbar
// appears. Kept deliberately above 1-2 chars so accidental double-click
// selections don't trigger the UI.
@@ -49,13 +81,17 @@ export const CONTEXT_MENU_ENTRIES: ContextMenuEntry[] = ACTIONS.flatMap((action)
style: 'Default' as WritingStyle,
title: `⚡ LexAI: ${ACTION_LABELS[action]}`,
},
...CONTEXT_MENU_STYLES.map((style) => ({
id: `lexai-${action}-${style.toLowerCase()}`,
action,
style,
parentId: `lexai-${action}`,
title: style as string,
})),
// 'prompt' opens the Prompt Builder dialog in the page, which has its own
// parameters — writing-style children don't apply to it.
...(action === 'prompt'
? []
: CONTEXT_MENU_STYLES.map((style) => ({
id: `lexai-${action}-${style.toLowerCase()}`,
action,
style,
parentId: `lexai-${action}`,
title: style as string,
}))),
]);
export function findContextMenuEntry(menuItemId: string): ContextMenuEntry | undefined {