mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
feat(ai-assist): first-use confirmation + wire Check-for-Updates to updater
Inline AI first-use confirmation (v4.13.0):
- inline-ai-controller accepts optional confirmFirstUse + getProviderLabel
deps. On the first action of the session, calls confirmFirstUse with
{providerLabel, selectionLength, action}; if it returns false, aborts
and shows error state. Once confirmed, never re-prompts for the
session.
- new 'ai-assistant:confirm-info' invoke handler returns
{provider, model} (no key material) so the renderer can label the
confirm dialog.
- electronAPI.aiAssistant.confirmInfo() exposed via preload.
- renderer.js wires window.confirm() with the provider label as the
v1 prompt UX; can be upgraded to a custom modal later without
touching the controller.
Check-for-Updates menu (v4.13.0):
- Help > Check for Updates now calls ensureAutoUpdater().check()
instead of opening the Releases page in the browser.
- Falls back to the browser page if the check throws (dev runs with
no app-update.yml, network failures, etc.) so the user always has
a way to update manually.
4 new controller tests cover: confirm called once with provider label
and selection length; declining aborts + surfaces error state; confirm
not asked twice in a session; missing confirmFirstUse dep skips the
prompt and streams normally.
Amit Haridas
This commit is contained in:
+22
-1
@@ -1475,8 +1475,19 @@ function createMenu() {
|
||||
click: () => shell.openExternal('https://github.com/amitwh/markdown-converter/issues'),
|
||||
},
|
||||
{
|
||||
// v4.13.0 — trigger the in-app updater (electron-updater).
|
||||
// If the check throws (no app-update.yml in dev, no network),
|
||||
// fall back to opening the Releases page in the browser so the
|
||||
// user still has a way to check manually.
|
||||
label: 'Check for Updates',
|
||||
click: () => shell.openExternal('https://github.com/amitwh/markdown-converter/releases'),
|
||||
click: async () => {
|
||||
try {
|
||||
await ensureAutoUpdater().check();
|
||||
} catch (err) {
|
||||
console.warn('Check-for-updates failed; falling back to releases page:', err.message);
|
||||
shell.openExternal('https://github.com/amitwh/markdown-converter/releases');
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -5153,6 +5164,16 @@ ipcMain.handle('ai-assistant:status', async () => {
|
||||
};
|
||||
});
|
||||
|
||||
// v4.13.0 — Inline AI first-use confirmation. Returns a small payload
|
||||
// (provider + model name; never the key) for the renderer's confirm
|
||||
// dialog. The renderer is responsible for asking the user.
|
||||
ipcMain.handle('ai-assistant:confirm-info', async () => {
|
||||
const settings = getAiAssistantSettings();
|
||||
const provider = settings.provider || 'unknown';
|
||||
const model = settings.model || AiProviders.PROVIDER_DEFAULTS[provider]?.defaultModel || '';
|
||||
return { provider, model };
|
||||
});
|
||||
|
||||
/**
|
||||
* Run a chat completion. The payload is {system?, messages:[{role,content}]}
|
||||
* with provider settings taken from the store, never from the caller — a
|
||||
|
||||
@@ -44,6 +44,9 @@ const ALLOWED_SEND_CHANNELS = [
|
||||
'ai-assistant:complete',
|
||||
'ai-assistant:status',
|
||||
|
||||
// Inline AI assist confirm helpers
|
||||
'ai-assistant:confirm-info',
|
||||
|
||||
// v4.13.0 — Inline AI assist streaming (Cmd+K in editor)
|
||||
'ai-assist-stream:start',
|
||||
'ai-assist-stream:cancel',
|
||||
@@ -597,6 +600,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
onStatus: (cb) => ipcRenderer.on('updates:status', (_e, p) => cb(p)),
|
||||
},
|
||||
|
||||
// v4.13.0 — Inline AI first-use confirmation. Returns a small
|
||||
// user-safe payload (no keys) describing what the AI request will do,
|
||||
// for the renderer to surface in a confirm dialog.
|
||||
aiAssistant: {
|
||||
confirmInfo: () => ipcRenderer.invoke('ai-assistant:confirm-info'),
|
||||
},
|
||||
|
||||
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
||||
});
|
||||
|
||||
|
||||
@@ -2604,9 +2604,20 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// CodeMirror view is sourced via tabManager each time Cmd+K fires, so
|
||||
// tab switches automatically pick up the right editor.
|
||||
let inlineAiControllerInstance = null;
|
||||
let inlineAiProviderLabel = 'your AI provider';
|
||||
function ensureInlineAiController() {
|
||||
if (inlineAiControllerInstance) return inlineAiControllerInstance;
|
||||
const create = getCreateInlineAiController();
|
||||
// Fetch provider label once (used in the first-use confirm dialog).
|
||||
window.electronAPI.aiAssistant
|
||||
.confirmInfo()
|
||||
.then((info) => {
|
||||
if (info && info.provider) {
|
||||
inlineAiProviderLabel = `${info.provider}${info.model ? ` (${info.model})` : ''}`;
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
inlineAiControllerInstance = create({
|
||||
getEditorView: () => {
|
||||
const active =
|
||||
@@ -2615,6 +2626,18 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
return active ? active.editorView : null;
|
||||
},
|
||||
electronAPI: window.electronAPI,
|
||||
// v4.13.0 — first-use confirmation. Once per session, then never
|
||||
// re-prompts. Uses native window.confirm for v1; can be upgraded
|
||||
// to a custom modal later without changing the controller.
|
||||
getProviderLabel: () => inlineAiProviderLabel,
|
||||
confirmFirstUse: ({ providerLabel, selectionLength, action }) => {
|
||||
const verb = action === 'rewrite' ? 'Rewrite' : action === 'shorten' ? 'Shorten' : 'Expand';
|
||||
return window.confirm(
|
||||
`${verb} ${selectionLength} characters using ${providerLabel}?\n\n` +
|
||||
'This sends your selected text to the AI provider. API charges may apply ' +
|
||||
'according to your provider plan. You will not be prompted again this session.'
|
||||
);
|
||||
},
|
||||
});
|
||||
return inlineAiControllerInstance;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ const {
|
||||
* Esc-cancel from firing when the popover isn't open.
|
||||
*/
|
||||
function createInlineAiController(deps) {
|
||||
const { getEditorView, electronAPI, onShortcut } = deps;
|
||||
const { getEditorView, electronAPI, onShortcut, confirmFirstUse, getProviderLabel } = deps;
|
||||
if (typeof getEditorView !== 'function') {
|
||||
throw new Error('createInlineAiController: getEditorView is required');
|
||||
}
|
||||
@@ -46,6 +46,7 @@ function createInlineAiController(deps) {
|
||||
let activeSelection = null; // { from, to, original }
|
||||
let activeAction = null; // 'rewrite' | 'shorten' | 'expand'
|
||||
let unsubscribers = []; // [{ off }]
|
||||
let firstUseConfirmed = false; // session-scoped — never re-prompts after OK
|
||||
|
||||
function ensurePopover() {
|
||||
if (popover) return popover;
|
||||
@@ -113,6 +114,25 @@ function createInlineAiController(deps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First-use confirmation — only on the first action of the session,
|
||||
// and only when the renderer supplied a confirmFirstUse callback.
|
||||
if (!firstUseConfirmed && typeof confirmFirstUse === 'function') {
|
||||
const providerLabel =
|
||||
typeof getProviderLabel === 'function' ? getProviderLabel() : 'your configured AI provider';
|
||||
const ok = confirmFirstUse({
|
||||
providerLabel,
|
||||
selectionLength: selectionText.length,
|
||||
action,
|
||||
});
|
||||
if (!ok) {
|
||||
ensurePopover().setState('error', {
|
||||
message: 'Cancelled — first-use confirmation declined.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
firstUseConfirmed = true;
|
||||
}
|
||||
|
||||
activeAction = action;
|
||||
ensurePopover().setState('loading');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user