From d240e8fdc867589af8b4a0e2c3fe04f2d5e1ee65 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 9 Sep 2024 12:35:24 +0200 Subject: [PATCH] #846 - Add GH Copilot action for title field --- CHANGELOG.md | 1 + src/helpers/Questions.ts | 96 +++++++++++-------- src/listeners/panel/DataListener.ts | 21 +++- src/panelWebView/CommandToCode.ts | 1 + .../components/Fields/TextField.tsx | 27 +++++- src/services/Copilot.ts | 2 +- 6 files changed, 102 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6ccbf1d..141fedd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#833](https://github.com/estruyf/vscode-front-matter/issues/833): Added support for Asciidoc files - [#834](https://github.com/estruyf/vscode-front-matter/issues/834): Added the ability to create new data files for a data folder +- [#846](https://github.com/estruyf/vscode-front-matter/issues/846): Added GitHub Copilot action for title field ### 🐞 Fixes diff --git a/src/helpers/Questions.ts b/src/helpers/Questions.ts index f32b2386..9eba67f0 100644 --- a/src/helpers/Questions.ts +++ b/src/helpers/Questions.ts @@ -93,46 +93,12 @@ export class Questions { } } - if (title && aiTitles && aiTitles.length > 0) { - const options: QuickPickItem[] = [ - { - label: `✏️ ${l10n.t( - LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickTitleSeparator - )}`, - kind: QuickPickItemKind.Separator - }, - { - label: title - }, - { - label: `🤖 ${l10n.t( - isCopilotInstalled - ? LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickCopilotSeparator - : LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickAiSeparator - )}`, - kind: QuickPickItemKind.Separator - }, - ...aiTitles.map((d: string) => ({ - label: d - })) - ]; - - const selectedTitle = await window.showQuickPick(options, { - title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectTitle), - placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectPlaceholder), - ignoreFocusOut: true - }); - - if (selectedTitle) { - title = selectedTitle.label; - } else if (!selectedTitle) { - // Reset the title, so the user can enter their own title - title = undefined; - } - } else if (!title && showWarning) { - Notifications.warning(l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputWarning)); - return; - } + title = await this.pickTitleSuggestions( + title, + aiTitles || [], + isCopilotInstalled, + showWarning + ); } if (!title) { @@ -152,6 +118,56 @@ export class Questions { return title; } + public static async pickTitleSuggestions( + title: string | undefined, + aiTitles: string[], + isCopilotInstalled: boolean, + showWarning: boolean = true + ): Promise { + if (title && aiTitles && aiTitles.length > 0) { + const options: QuickPickItem[] = [ + { + label: `✏️ ${l10n.t( + LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickTitleSeparator + )}`, + kind: QuickPickItemKind.Separator + }, + { + label: title + }, + { + label: `🤖 ${l10n.t( + isCopilotInstalled + ? LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickCopilotSeparator + : LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickAiSeparator + )}`, + kind: QuickPickItemKind.Separator + }, + ...aiTitles.map((d: string) => ({ + label: d + })) + ]; + + const selectedTitle = await window.showQuickPick(options, { + title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectTitle), + placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputSelectPlaceholder), + ignoreFocusOut: true + }); + + if (selectedTitle) { + title = selectedTitle.label; + } else if (!selectedTitle) { + // Reset the title, so the user can enter their own title + title = undefined; + } + } else if (!title && showWarning) { + Notifications.warning(l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputWarning)); + return; + } + + return title; + } + /** * Select the folder for your content creation * @param showWarning diff --git a/src/listeners/panel/DataListener.ts b/src/listeners/panel/DataListener.ts index 213ea204..df2694c9 100644 --- a/src/listeners/panel/DataListener.ts +++ b/src/listeners/panel/DataListener.ts @@ -15,7 +15,8 @@ import { processArticlePlaceholdersFromData, processTimePlaceholders, processFmPlaceholders, - parseWinPath + parseWinPath, + Questions } from '../../helpers'; import { COMMAND_NAME, @@ -107,6 +108,24 @@ export class DataListener extends BaseListener { case CommandToCode.copilotSuggestDescription: this.copilotSuggestDescription(msg.command, msg.requestId); break; + case CommandToCode.copilotSuggestTitle: + this.copilotSuggestTitle(msg.command, msg.requestId, msg.payload); + break; + } + } + + private static async copilotSuggestTitle(command: string, requestId?: string, title?: string) { + if (!command || !requestId || !title) { + return; + } + + const aiTitles = await Copilot.suggestTitles(title); + title = await Questions.pickTitleSuggestions(title, aiTitles || [], true); + + if (title) { + this.sendRequest(command, requestId, title); + } else { + this.sendRequestError(command, requestId, 'Failed to suggest title'); } } diff --git a/src/panelWebView/CommandToCode.ts b/src/panelWebView/CommandToCode.ts index 999699be..1063c61b 100644 --- a/src/panelWebView/CommandToCode.ts +++ b/src/panelWebView/CommandToCode.ts @@ -42,6 +42,7 @@ export enum CommandToCode { stopServer = 'stop-server', aiSuggestTaxonomy = 'ai-suggest-taxonomy', aiSuggestDescription = 'ai-suggest-description', + copilotSuggestTitle = 'copilot-suggest-title', copilotSuggestDescription = 'copilot-suggest-description', copilotSuggestTaxonomy = 'copilot-suggest-taxonomy', searchByType = 'search-by-type', diff --git a/src/panelWebView/components/Fields/TextField.tsx b/src/panelWebView/components/Fields/TextField.tsx index 1b6252cb..e0af4ca3 100644 --- a/src/panelWebView/components/Fields/TextField.tsx +++ b/src/panelWebView/components/Fields/TextField.tsx @@ -93,6 +93,25 @@ export const TextField: React.FunctionComponent = ({ } }, [showRequiredState, isValid]); + + const suggestTitle = () => { + setLoading(localize(LocalizationKey.panelFieldsTextFieldAiGenerate)); + + messageHandler + .request(CommandToCode.copilotSuggestTitle, text) + .then((suggestion) => { + setLoading(undefined); + + if (suggestion) { + setText(suggestion); + onChange(suggestion); + } + }) + .catch(() => { + setLoading(undefined); + }); + }; + const suggestDescription = (type: 'ai' | 'copilot') => { setLoading(localize(LocalizationKey.panelFieldsTextFieldAiGenerate)); @@ -114,13 +133,13 @@ export const TextField: React.FunctionComponent = ({ }; const actionElement = useMemo(() => { - if (settings.seo.descriptionField !== name) { + if (settings.seo.descriptionField !== name && settings.seo.titleField !== name) { return; } return ( <> - {settings?.aiEnabled && ( + {settings?.aiEnabled && settings.seo.descriptionField === name && (