#846 - Add GH Copilot action for title field

This commit is contained in:
Elio Struyf
2024-09-09 12:35:24 +02:00
parent e95e9a8fc7
commit d240e8fdc8
6 changed files with 102 additions and 46 deletions

View File

@@ -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

View File

@@ -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<string | undefined> {
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

View File

@@ -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');
}
}

View File

@@ -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',

View File

@@ -93,6 +93,25 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
}
}, [showRequiredState, isValid]);
const suggestTitle = () => {
setLoading(localize(LocalizationKey.panelFieldsTextFieldAiGenerate));
messageHandler
.request<string>(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<ITextFieldProps> = ({
};
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 && (
<button
className="metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50"
title={localize(LocalizationKey.panelFieldsTextFieldAiMessage, label?.toLowerCase())}
@@ -137,7 +156,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
className="metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50"
title={localize(LocalizationKey.panelFieldsTextFieldCopilotMessage, label?.toLowerCase())}
type="button"
onClick={() => suggestDescription('copilot')}
onClick={() => settings.seo.descriptionField === name ? suggestDescription('copilot') : suggestTitle()}
disabled={!!loading}
>
<CopilotIcon />
@@ -145,7 +164,7 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
)}
</>
);
}, [settings?.aiEnabled, settings?.copilotEnabled, name, actions, loading]);
}, [settings?.aiEnabled, settings?.copilotEnabled, settings?.seo, name, actions, loading]);
useEffect(() => {
if (text !== value && (lastUpdated === null || Date.now() - DEBOUNCE_TIME > lastUpdated)) {

View File

@@ -47,7 +47,7 @@ export class Copilot {
IMPORTANT: You are only allowed to respond with a text that should not exceed ${chars} characters in length.
Desired format: just a string, e.g. "My first blog post". Each suggestion is separated by a new line.`
Desired format: just a string and wrapped in double quotes, e.g. "My first blog post". Each suggestion is separated by a new line.`
),
LanguageModelChatMessage.User(`The title of the blog post is """${title}""".`)
];