mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-07-06 01:41:48 +02:00
Merge branch 'copilot-integration' into dev
This commit is contained in:
@@ -431,6 +431,7 @@
|
||||
"panel.fields.slugField.generate": "Generate slug",
|
||||
|
||||
"panel.fields.textField.ai.message": "Use Front Matter AI to suggest {0}",
|
||||
"panel.fields.textField.copilot.message": "Use Copilot to suggest {0}",
|
||||
"panel.fields.textField.ai.generate": "Generating suggestion...",
|
||||
"panel.fields.textField.loading": "Loading field",
|
||||
"panel.fields.textField.limit": "Field limit reached {0}",
|
||||
@@ -521,6 +522,7 @@
|
||||
"panel.tagPicker.inputPlaceholder.empty": "Pick your {0}",
|
||||
"panel.tagPicker.inputPlaceholder.disabled": "You have reached the limit of {0}",
|
||||
"panel.tagPicker.ai.suggest": "Use Front Matter AI to suggest {0}",
|
||||
"panel.tagPicker.copilot.suggest": "Use GitHub Copilot to suggest {0}",
|
||||
"panel.tagPicker.ai.generating": "Generating suggestions...",
|
||||
"panel.tagPicker.limit": "Max.: {0}",
|
||||
"panel.tagPicker.unkown": "Add the unknown tag",
|
||||
@@ -704,9 +706,11 @@
|
||||
"helpers.questions.contentTitle.aiInput.placeholder": "What would you like to write about?",
|
||||
"helpers.questions.contentTitle.aiInput.quickPick.title.separator": "your title/description",
|
||||
"helpers.questions.contentTitle.aiInput.quickPick.ai.separator": "AI generated title",
|
||||
"helpers.questions.contentTitle.aiInput.quickPick.copilot.separator": "GitHub Copilot suggestions",
|
||||
"helpers.questions.contentTitle.aiInput.select.title": "Select a title",
|
||||
"helpers.questions.contentTitle.aiInput.select.placeholder": "Select a title for your content",
|
||||
"helpers.questions.contentTitle.aiInput.failed": "Failed fetching the AI title. Please try to use your own title or try again later.",
|
||||
"helpers.questions.contentTitle.copilotInput.failed": "Failed fetching the GitHub Copilot title suggestions. Please try to use your own title or try again later.",
|
||||
"helpers.questions.contentTitle.aiInput.warning": "You did not specify a title for your content.",
|
||||
"helpers.questions.contentTitle.titleInput.title": "Content title",
|
||||
"helpers.questions.contentTitle.titleInput.prompt": "What would you like to use as a title for the content to create?",
|
||||
@@ -780,6 +784,8 @@
|
||||
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noEditor.error": "No active editor",
|
||||
"listeners.panel.taxonomyListener.aiSuggestTaxonomy.noData.error": "No article data",
|
||||
|
||||
"services.copilot.getChatResponse.error": "Failed to get a response from the GitHub Copilot.",
|
||||
|
||||
"services.modeSwitch.switchMode.quickPick.placeholder": "Select the mode you want to use",
|
||||
"services.modeSwitch.switchMode.quickPick.title": "{0}: Mode selection",
|
||||
"services.modeSwitch.setText.mode": "Mode: {0}",
|
||||
|
||||
Generated
+18
-18
@@ -33,7 +33,7 @@
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react-datepicker": "^4.8.0",
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/vscode": "^1.73.0",
|
||||
"@types/vscode": "^1.90.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vscode-elements/elements": "^1.2.0",
|
||||
@@ -110,7 +110,7 @@
|
||||
"yawn-yaml": "^1.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.73.0"
|
||||
"vscode": "^1.90.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -2084,9 +2084,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.86.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.86.0.tgz",
|
||||
"integrity": "sha512-DnIXf2ftWv+9LWOB5OJeIeaLigLHF7fdXF6atfc7X5g2w/wVZBgk0amP7b+ub5xAuW1q7qP5YcFvOcit/DtyCQ==",
|
||||
"version": "1.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz",
|
||||
"integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/vscode-webview": {
|
||||
@@ -3100,12 +3100,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -4760,9 +4760,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
@@ -12698,9 +12698,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/ws": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
|
||||
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -12946,9 +12946,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||
"version": "7.5.10",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
|
||||
+187
-148
@@ -10,7 +10,8 @@
|
||||
"color": "#0e131f",
|
||||
"theme": "dark"
|
||||
},
|
||||
"badges": [{
|
||||
"badges": [
|
||||
{
|
||||
"description": "version",
|
||||
"url": "https://img.shields.io/github/package-json/v/estruyf/vscode-front-matter?color=green&label=vscode-front-matter&style=flat-square",
|
||||
"href": "https://github.com/estruyf/vscode-front-matter"
|
||||
@@ -26,10 +27,11 @@
|
||||
},
|
||||
"qna": "https://github.com/estruyf/vscode-front-matter/discussions",
|
||||
"engines": {
|
||||
"vscode": "^1.73.0"
|
||||
"vscode": "^1.90.0"
|
||||
},
|
||||
"l10n": "./l10n",
|
||||
"categories": [
|
||||
"AI",
|
||||
"Other"
|
||||
],
|
||||
"keywords": [
|
||||
@@ -69,7 +71,8 @@
|
||||
"**/.frontmatter/config/*.json": "jsonc"
|
||||
}
|
||||
},
|
||||
"keybindings": [{
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "frontMatter.dashboard",
|
||||
"key": "alt+d"
|
||||
},
|
||||
@@ -93,19 +96,23 @@
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}]
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "frontmatter-explorer",
|
||||
"title": "FM",
|
||||
"icon": "$(fm-logo)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"frontmatter-explorer": [{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}]
|
||||
"frontmatter-explorer": [
|
||||
{
|
||||
"id": "frontMatter.explorer",
|
||||
"name": "Front Matter",
|
||||
"icon": "$(fm-logo)",
|
||||
"type": "webview"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "%settings.configuration.title%",
|
||||
@@ -173,7 +180,8 @@
|
||||
"frontMatter.content.defaultFileType": {
|
||||
"type": "string",
|
||||
"default": "md",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"md",
|
||||
"mdx"
|
||||
@@ -189,7 +197,8 @@
|
||||
"frontMatter.content.defaultSorting": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"oneOf": [{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"LastModifiedAsc",
|
||||
"LastModifiedDesc",
|
||||
@@ -541,7 +550,8 @@
|
||||
"categories"
|
||||
],
|
||||
"markdownDescription": "%setting.frontMatter.content.filters.markdownDescription%",
|
||||
"items": [{
|
||||
"items": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"contentFolders",
|
||||
@@ -614,7 +624,8 @@
|
||||
"command": {
|
||||
"$id": "#scriptCommand",
|
||||
"type": "string",
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"node",
|
||||
"bash",
|
||||
@@ -810,7 +821,8 @@
|
||||
"title",
|
||||
"file"
|
||||
],
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -864,7 +876,8 @@
|
||||
"id",
|
||||
"path"
|
||||
],
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"schema"
|
||||
]
|
||||
@@ -1105,26 +1118,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"fileTypes": null,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Caption",
|
||||
"name": "caption",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Alt text",
|
||||
"name": "alt",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"fileTypes": null,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Caption",
|
||||
"name": "caption",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Alt text",
|
||||
"name": "alt",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scope": "Media"
|
||||
},
|
||||
"frontMatter.media.supportedMimeTypes": {
|
||||
@@ -1360,7 +1376,8 @@
|
||||
"default": "",
|
||||
"description": "%setting.frontMatter.taxonomy.contentTypes.items.properties.fields.items.properties.taxonomyId.description%",
|
||||
"not": {
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -1554,7 +1571,8 @@
|
||||
"type",
|
||||
"name"
|
||||
],
|
||||
"allOf": [{
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
@@ -1766,48 +1784,51 @@
|
||||
"fields"
|
||||
]
|
||||
},
|
||||
"default": [{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"default": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scope": "Taxonomy"
|
||||
},
|
||||
"frontMatter.taxonomy.customTaxonomy": {
|
||||
@@ -1820,7 +1841,8 @@
|
||||
"type": "string",
|
||||
"description": "%setting.frontMatter.taxonomy.customTaxonomy.items.properties.id.description%",
|
||||
"not": {
|
||||
"anyOf": [{
|
||||
"anyOf": [
|
||||
{
|
||||
"const": ""
|
||||
},
|
||||
{
|
||||
@@ -2015,7 +2037,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [{
|
||||
"commands": [
|
||||
{
|
||||
"command": "frontMatter.project.switch",
|
||||
"title": "%command.frontMatter.project.switch%",
|
||||
"category": "Front Matter",
|
||||
@@ -2347,16 +2370,21 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"submenus": [{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}],
|
||||
"submenus": [
|
||||
{
|
||||
"id": "frontmatter.submenu",
|
||||
"label": "Front Matter"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"webview/context": [{
|
||||
"command": "workbench.action.webview.openDeveloperTools",
|
||||
"when": "frontMatter:isDevelopment"
|
||||
}],
|
||||
"editor/title": [{
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "workbench.action.webview.openDeveloperTools",
|
||||
"when": "frontMatter:isDevelopment"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "frontMatter.markup.heading",
|
||||
"group": "navigation@-133",
|
||||
"when": "frontMatter:file:isValid == true && frontMatter:markdown:wysiwyg"
|
||||
@@ -2442,11 +2470,14 @@
|
||||
"when": "resourceFilename == 'frontmatter.json'"
|
||||
}
|
||||
],
|
||||
"explorer/context": [{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}],
|
||||
"frontmatter.submenu": [{
|
||||
"explorer/context": [
|
||||
{
|
||||
"submenu": "frontmatter.submenu",
|
||||
"group": "frontmatter@1"
|
||||
}
|
||||
],
|
||||
"frontmatter.submenu": [
|
||||
{
|
||||
"command": "frontMatter.createFromTemplate",
|
||||
"when": "explorerResourceIsFolder",
|
||||
"group": "frontmatter@1"
|
||||
@@ -2462,7 +2493,8 @@
|
||||
"group": "frontmatter@3"
|
||||
}
|
||||
],
|
||||
"commandPalette": [{
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "frontMatter.init",
|
||||
"when": "frontMatterCanInit"
|
||||
},
|
||||
@@ -2639,7 +2671,8 @@
|
||||
"when": "frontMatter:file:isValid == true"
|
||||
}
|
||||
],
|
||||
"view/title": [{
|
||||
"view/title": [
|
||||
{
|
||||
"command": "frontMatter.docs",
|
||||
"group": "navigation@-1",
|
||||
"when": "view == frontMatter.explorer"
|
||||
@@ -2676,13 +2709,16 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"languages": [{
|
||||
"id": "frontmatter.project.output",
|
||||
"mimetypes": [
|
||||
"text/x-code-output"
|
||||
]
|
||||
}],
|
||||
"grammars": [{
|
||||
"languages": [
|
||||
{
|
||||
"id": "frontmatter.project.output",
|
||||
"mimetypes": [
|
||||
"text/x-code-output"
|
||||
]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"path": "./syntaxes/hugo.tmLanguage.json",
|
||||
"scopeName": "frontmatter.markdown.hugo",
|
||||
"injectTo": [
|
||||
@@ -2695,45 +2731,48 @@
|
||||
"path": "./syntaxes/frontmatter-output.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"walkthroughs": [{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
"walkthroughs": [
|
||||
{
|
||||
"id": "frontmatter.welcome",
|
||||
"title": "Get started with Front Matter",
|
||||
"description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.",
|
||||
"steps": [
|
||||
{
|
||||
"id": "frontmatter.welcome.init",
|
||||
"title": "Get started",
|
||||
"description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/get-started.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
},
|
||||
"completionEvents": [
|
||||
"onContext:frontMatterInitialized"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
{
|
||||
"id": "frontmatter.welcome.documentation",
|
||||
"title": "Documentation",
|
||||
"description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/documentation.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://frontmatter.codes/docs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
{
|
||||
"id": "frontmatter.welcome.supporter",
|
||||
"title": "Support the project",
|
||||
"description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)",
|
||||
"media": {
|
||||
"markdown": "assets/walkthrough/support-the-project.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onLink:https://github.com/sponsors/estruyf"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"dev:ext": "npm run clean && npm run localization:generate && npm-run-all --parallel watch:*",
|
||||
@@ -2779,7 +2818,7 @@
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react-datepicker": "^4.8.0",
|
||||
"@types/react-dom": "17.0.0",
|
||||
"@types/vscode": "^1.73.0",
|
||||
"@types/vscode": "^1.90.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vscode-elements/elements": "^1.2.0",
|
||||
@@ -2861,4 +2900,4 @@
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
TaxonomyType
|
||||
} from '../models';
|
||||
import { Folders } from '../commands';
|
||||
import { Copilot } from '../services/Copilot';
|
||||
|
||||
export class PanelSettings {
|
||||
public static async get(): Promise<IPanelSettings> {
|
||||
@@ -53,6 +54,7 @@ export class PanelSettings {
|
||||
try {
|
||||
return {
|
||||
aiEnabled: Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED) || false,
|
||||
copilotEnabled: await Copilot.isInstalled(),
|
||||
git: await GitListener.getSettings(),
|
||||
seo: {
|
||||
title: (Settings.get(SETTING_SEO_TITLE_LENGTH) as number) || -1,
|
||||
|
||||
+75
-46
@@ -9,6 +9,7 @@ import { SponsorAi } from '../services/SponsorAI';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../localization';
|
||||
import { ContentFolder } from '../models';
|
||||
import { Copilot } from '../services/Copilot';
|
||||
|
||||
interface FolderQuickPickItem extends QuickPickItem {
|
||||
path: string;
|
||||
@@ -41,11 +42,12 @@ export class Questions {
|
||||
public static async ContentTitle(showWarning: boolean = true): Promise<string | undefined> {
|
||||
const aiEnabled = Settings.get<boolean>(SETTING_SPONSORS_AI_ENABLED);
|
||||
let title: string | undefined = '';
|
||||
const isCopilotInstalled = await Copilot.isInstalled();
|
||||
|
||||
if (aiEnabled) {
|
||||
const githubAuth = await authentication.getSession('github', ['read:user'], { silent: true });
|
||||
let aiTitles: string[] | undefined;
|
||||
|
||||
if (githubAuth && githubAuth.account.label) {
|
||||
if (aiEnabled || isCopilotInstalled) {
|
||||
if (isCopilotInstalled) {
|
||||
title = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputTitle),
|
||||
prompt: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPrompt),
|
||||
@@ -55,54 +57,81 @@ export class Questions {
|
||||
|
||||
if (title) {
|
||||
try {
|
||||
const aiTitles = await SponsorAi.getTitles(githubAuth.accessToken, title);
|
||||
|
||||
if (aiTitles && aiTitles.length > 0) {
|
||||
const options: QuickPickItem[] = [
|
||||
{
|
||||
label: `✏️ ${l10n.t(
|
||||
LocalizationKey.helpersQuestionsContentTitleAiInputQuickPickTitleSeparator
|
||||
)}`,
|
||||
kind: QuickPickItemKind.Separator
|
||||
},
|
||||
{
|
||||
label: title
|
||||
},
|
||||
{
|
||||
label: `🤖 ${l10n.t(
|
||||
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;
|
||||
}
|
||||
}
|
||||
aiTitles = await Copilot.suggestTitles(title);
|
||||
} catch (e) {
|
||||
Logger.error((e as Error).message);
|
||||
Notifications.error(l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputFailed));
|
||||
Notifications.error(
|
||||
l10n.t(LocalizationKey.helpersQuestionsContentTitleCopilotInputFailed)
|
||||
);
|
||||
title = undefined;
|
||||
}
|
||||
} else if (!title && showWarning) {
|
||||
Notifications.warning(l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputWarning));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const githubAuth = await authentication.getSession('github', ['read:user'], {
|
||||
silent: true
|
||||
});
|
||||
|
||||
if (githubAuth && githubAuth.account.label) {
|
||||
title = await window.showInputBox({
|
||||
title: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputTitle),
|
||||
prompt: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPrompt),
|
||||
placeHolder: l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputPlaceholder),
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
|
||||
if (title) {
|
||||
try {
|
||||
aiTitles = await SponsorAi.getTitles(githubAuth.accessToken, title);
|
||||
} catch (e) {
|
||||
Logger.error((e as Error).message);
|
||||
Notifications.error(
|
||||
l10n.t(LocalizationKey.helpersQuestionsContentTitleAiInputFailed)
|
||||
);
|
||||
title = 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,12 @@ import { Folders } from '../../commands/Folders';
|
||||
import { Command } from '../../panelWebView/Command';
|
||||
import { CommandToCode } from '../../panelWebView/CommandToCode';
|
||||
import { BaseListener } from './BaseListener';
|
||||
import { Uri, authentication, commands, window } from 'vscode';
|
||||
import {
|
||||
Uri,
|
||||
authentication,
|
||||
commands,
|
||||
window
|
||||
} from 'vscode';
|
||||
import {
|
||||
ArticleHelper,
|
||||
Extension,
|
||||
@@ -45,6 +50,7 @@ import { Terminal } from '../../services';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { parse } from 'path';
|
||||
import { Copilot } from '../../services/Copilot';
|
||||
|
||||
const FILE_LIMIT = 10;
|
||||
|
||||
@@ -104,9 +110,62 @@ export class DataListener extends BaseListener {
|
||||
case CommandToCode.aiSuggestDescription:
|
||||
this.aiSuggestTaxonomy(msg.command, msg.requestId);
|
||||
break;
|
||||
case CommandToCode.copilotSuggestDescription:
|
||||
this.copilotSuggestDescription(msg.command, msg.requestId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a Copilot suggestion command.
|
||||
* @param command - The command to execute.
|
||||
* @param requestId - The optional request ID.
|
||||
* @returns A Promise that resolves when the suggestion command is executed.
|
||||
*/
|
||||
private static async copilotSuggestDescription(command: string, requestId?: string) {
|
||||
if (!command || !requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getActiveFile();
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
|
||||
const articleDetails = await ArticleHelper.getFrontMatterByPath(article);
|
||||
if (!articleDetails) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = PanelProvider.getInstance(extPath);
|
||||
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const description = await Copilot.suggestDescription(
|
||||
articleDetails.data[titleField],
|
||||
articleDetails.content
|
||||
);
|
||||
|
||||
if (description) {
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
payload: description
|
||||
} as MessageHandlerData<string>);
|
||||
} else {
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
error: l10n.t(LocalizationKey.servicesCopilotGetChatResponseError)
|
||||
} as MessageHandlerData<string>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests taxonomy using AI.
|
||||
* @param command - The command string.
|
||||
* @param requestId - The optional request ID.
|
||||
*/
|
||||
private static async aiSuggestTaxonomy(command: string, requestId?: string) {
|
||||
if (!command || !requestId) {
|
||||
return;
|
||||
|
||||
@@ -15,6 +15,7 @@ import { PanelProvider } from '../../panelWebView/PanelProvider';
|
||||
import { MessageHandlerData } from '@estruyf/vscode';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../localization';
|
||||
import { Copilot } from '../../services/Copilot';
|
||||
|
||||
export class TaxonomyListener extends BaseListener {
|
||||
/**
|
||||
@@ -66,9 +67,72 @@ export class TaxonomyListener extends BaseListener {
|
||||
case CommandToCode.aiSuggestTaxonomy:
|
||||
this.aiSuggestTaxonomy(msg.command, msg.requestId, msg.payload);
|
||||
break;
|
||||
case CommandToCode.copilotSuggestTaxonomy:
|
||||
this.copilotSuggestTaxonomy(msg.command, msg.requestId, msg.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Suggests a taxonomy for a given command, request ID, and tag type.
|
||||
*
|
||||
* @param command - The command to execute.
|
||||
* @param requestId - The ID of the request.
|
||||
* @param type - The type of the tag.
|
||||
* @returns A Promise that resolves to void.
|
||||
*/
|
||||
private static async copilotSuggestTaxonomy(command: string, requestId?: string, type?: TagType) {
|
||||
if (!command || !requestId || !type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const article = ArticleHelper.getActiveFile();
|
||||
if (!article) {
|
||||
return;
|
||||
}
|
||||
|
||||
const articleDetails = await ArticleHelper.getFrontMatterByPath(article);
|
||||
if (!articleDetails) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extPath = Extension.getInstance().extensionPath;
|
||||
const panel = PanelProvider.getInstance(extPath);
|
||||
|
||||
const titleField = (Settings.get(SETTING_SEO_TITLE_FIELD) as string) || DefaultFields.Title;
|
||||
const descriptionField = (Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string) || DefaultFields.Description;
|
||||
|
||||
const tags = await Copilot.suggestTaxonomy(
|
||||
articleDetails.data[titleField],
|
||||
type,
|
||||
articleDetails.data[descriptionField],
|
||||
articleDetails.content
|
||||
);
|
||||
|
||||
if (tags) {
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
payload: tags
|
||||
} as MessageHandlerData<string[]>);
|
||||
} else {
|
||||
panel.getWebview()?.postMessage({
|
||||
command,
|
||||
requestId,
|
||||
error: l10n.t(LocalizationKey.servicesCopilotGetChatResponseError)
|
||||
} as MessageHandlerData<string>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests taxonomy based on the provided command, request ID, and tag type.
|
||||
*
|
||||
* @param command - The command to execute.
|
||||
* @param requestId - The ID of the request.
|
||||
* @param type - The type of tag.
|
||||
* @returns A Promise that resolves to void.
|
||||
*/
|
||||
private static async aiSuggestTaxonomy(command: string, requestId?: string, type?: TagType) {
|
||||
if (!command || !requestId || !type) {
|
||||
return;
|
||||
|
||||
@@ -1396,6 +1396,10 @@ export enum LocalizationKey {
|
||||
* Use Front Matter AI to suggest {0}
|
||||
*/
|
||||
panelFieldsTextFieldAiMessage = 'panel.fields.textField.ai.message',
|
||||
/**
|
||||
* Use Copilot to suggest {0}
|
||||
*/
|
||||
panelFieldsTextFieldCopilotMessage = 'panel.fields.textField.copilot.message',
|
||||
/**
|
||||
* Generating suggestion...
|
||||
*/
|
||||
@@ -1676,6 +1680,10 @@ export enum LocalizationKey {
|
||||
* Use Front Matter AI to suggest {0}
|
||||
*/
|
||||
panelTagPickerAiSuggest = 'panel.tagPicker.ai.suggest',
|
||||
/**
|
||||
* Use GitHub Copilot to suggest {0}
|
||||
*/
|
||||
panelTagPickerCopilotSuggest = 'panel.tagPicker.copilot.suggest',
|
||||
/**
|
||||
* Generating suggestions...
|
||||
*/
|
||||
@@ -2312,6 +2320,10 @@ export enum LocalizationKey {
|
||||
* AI generated title
|
||||
*/
|
||||
helpersQuestionsContentTitleAiInputQuickPickAiSeparator = 'helpers.questions.contentTitle.aiInput.quickPick.ai.separator',
|
||||
/**
|
||||
* GitHub Copilot generated title
|
||||
*/
|
||||
helpersQuestionsContentTitleAiInputQuickPickCopilotSeparator = 'helpers.questions.contentTitle.aiInput.quickPick.copilot.separator',
|
||||
/**
|
||||
* Select a title
|
||||
*/
|
||||
@@ -2324,6 +2336,10 @@ export enum LocalizationKey {
|
||||
* Failed fetching the AI title. Please try to use your own title or try again later.
|
||||
*/
|
||||
helpersQuestionsContentTitleAiInputFailed = 'helpers.questions.contentTitle.aiInput.failed',
|
||||
/**
|
||||
* Failed fetching the GitHub Copilot title suggestions. Please try to use your own title or try again later.
|
||||
*/
|
||||
helpersQuestionsContentTitleCopilotInputFailed = 'helpers.questions.contentTitle.copilotInput.failed',
|
||||
/**
|
||||
* You did not specify a title for your content.
|
||||
*/
|
||||
@@ -2572,6 +2588,10 @@ export enum LocalizationKey {
|
||||
* No article data
|
||||
*/
|
||||
listenersPanelTaxonomyListenerAiSuggestTaxonomyNoDataError = 'listeners.panel.taxonomyListener.aiSuggestTaxonomy.noData.error',
|
||||
/**
|
||||
* Failed to get a response from the GitHub Copilot.
|
||||
*/
|
||||
servicesCopilotGetChatResponseError = 'services.copilot.getChatResponse.error',
|
||||
/**
|
||||
* Select the mode you want to use
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface PanelSettings {
|
||||
fieldGroups: FieldGroup[] | undefined;
|
||||
commaSeparatedFields: string[];
|
||||
aiEnabled: boolean;
|
||||
copilotEnabled: boolean;
|
||||
contentFolders: ContentFolder[];
|
||||
websiteUrl: string;
|
||||
disabledActions: PanelAction[];
|
||||
|
||||
@@ -42,6 +42,8 @@ export enum CommandToCode {
|
||||
stopServer = 'stop-server',
|
||||
aiSuggestTaxonomy = 'ai-suggest-taxonomy',
|
||||
aiSuggestDescription = 'ai-suggest-description',
|
||||
copilotSuggestDescription = 'copilot-suggest-description',
|
||||
copilotSuggestTaxonomy = 'copilot-suggest-taxonomy',
|
||||
searchByType = 'search-by-type',
|
||||
processMediaData = 'process-media-data',
|
||||
isServerStarted = 'is-server-started'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { RequiredAsterix } from './RequiredAsterix';
|
||||
import { VSCodeLabel } from '../VSCode';
|
||||
|
||||
export interface IFieldTitleProps {
|
||||
label: string | JSX.Element;
|
||||
@@ -23,16 +22,14 @@ export const FieldTitle: React.FunctionComponent<IFieldTitleProps> = ({
|
||||
}, [icon]);
|
||||
|
||||
return (
|
||||
<VSCodeLabel>
|
||||
<div className={`metadata_field__label ${className || ''}`}>
|
||||
<div>
|
||||
{Icon}
|
||||
<span style={{ lineHeight: '16px' }}>{label}</span>
|
||||
<RequiredAsterix required={required} />
|
||||
</div>
|
||||
<div className='flex items-center justify-between w-full mb-2'>
|
||||
<label className={`metadata_field__label text-base text-[var(--vscode-foreground)] ${className || ''}`}>
|
||||
{Icon}
|
||||
<span style={{ lineHeight: '16px' }}>{label}</span>
|
||||
<RequiredAsterix required={required} />
|
||||
</label>
|
||||
|
||||
{actionElement}
|
||||
</div>
|
||||
</VSCodeLabel>
|
||||
{actionElement}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import { SparklesIcon } from '@heroicons/react/24/outline';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import useDropdownStyle from '../../hooks/useDropdownStyle';
|
||||
import { CopilotIcon } from '../Icons';
|
||||
|
||||
export interface ITagPickerProps {
|
||||
type: TagType;
|
||||
@@ -98,39 +99,42 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
* Send an update to VSCode
|
||||
* @param values
|
||||
*/
|
||||
const sendUpdate = useCallback((values: string[]) => {
|
||||
if (type === TagType.tags) {
|
||||
Messenger.send(CommandToCode.updateTags, {
|
||||
fieldName,
|
||||
values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
});
|
||||
} else if (type === TagType.categories) {
|
||||
Messenger.send(CommandToCode.updateCategories, {
|
||||
fieldName,
|
||||
values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
});
|
||||
} else if (type === TagType.keywords) {
|
||||
Messenger.send(CommandToCode.updateKeywords, {
|
||||
values,
|
||||
parents
|
||||
});
|
||||
} else if (type === TagType.custom) {
|
||||
Messenger.send(CommandToCode.updateCustomTaxonomy, {
|
||||
id: taxonomyId,
|
||||
name: fieldName,
|
||||
options: values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
} as CustomTaxonomyData);
|
||||
}
|
||||
}, [renderAsString]);
|
||||
const sendUpdate = useCallback(
|
||||
(values: string[]) => {
|
||||
if (type === TagType.tags) {
|
||||
Messenger.send(CommandToCode.updateTags, {
|
||||
fieldName,
|
||||
values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
});
|
||||
} else if (type === TagType.categories) {
|
||||
Messenger.send(CommandToCode.updateCategories, {
|
||||
fieldName,
|
||||
values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
});
|
||||
} else if (type === TagType.keywords) {
|
||||
Messenger.send(CommandToCode.updateKeywords, {
|
||||
values,
|
||||
parents
|
||||
});
|
||||
} else if (type === TagType.custom) {
|
||||
Messenger.send(CommandToCode.updateCustomTaxonomy, {
|
||||
id: taxonomyId,
|
||||
name: fieldName,
|
||||
options: values,
|
||||
renderAsString,
|
||||
parents,
|
||||
blockData
|
||||
} as CustomTaxonomyData);
|
||||
}
|
||||
},
|
||||
[renderAsString]
|
||||
);
|
||||
|
||||
/**
|
||||
* Triggers the focus to the input when command is executed
|
||||
@@ -182,19 +186,24 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
* @param option
|
||||
* @param inputValue
|
||||
*/
|
||||
const filterList = useCallback((option: string, inputValue: string | null) => {
|
||||
if (typeof option !== 'string') {
|
||||
return false;
|
||||
}
|
||||
const filterList = useCallback(
|
||||
(option: string, inputValue: string | null) => {
|
||||
if (typeof option !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(selected instanceof Array)) {
|
||||
return true;
|
||||
}
|
||||
if (!(selected instanceof Array)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
option && !selected.includes(option) && option.toLowerCase().includes((inputValue || '').toLowerCase())
|
||||
);
|
||||
}, [selected]);
|
||||
return (
|
||||
option &&
|
||||
!selected.includes(option) &&
|
||||
option.toLowerCase().includes((inputValue || '').toLowerCase())
|
||||
);
|
||||
},
|
||||
[selected]
|
||||
);
|
||||
|
||||
/**
|
||||
* Add the new item to the data
|
||||
@@ -240,20 +249,29 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
[options, inputRef, selected, freeform]
|
||||
);
|
||||
|
||||
const suggestTaxonomy = useCallback((type: TagType) => {
|
||||
setLoading(true);
|
||||
messageHandler.request<string[]>(CommandToCode.aiSuggestTaxonomy, type).then((values) => {
|
||||
setLoading(false);
|
||||
if (values && values instanceof Array && values.length > 0) {
|
||||
const uniqValues = Array.from(new Set([...selected, ...values]));
|
||||
setSelected(uniqValues);
|
||||
sendUpdate(uniqValues);
|
||||
setInputValue('');
|
||||
}
|
||||
}).catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [selected]);
|
||||
const suggestTaxonomy = useCallback(
|
||||
(aiType: 'ai' | 'copilot', type: TagType) => {
|
||||
setLoading(true);
|
||||
|
||||
const command =
|
||||
aiType === 'ai' ? CommandToCode.aiSuggestTaxonomy : CommandToCode.copilotSuggestTaxonomy;
|
||||
messageHandler
|
||||
.request<string[]>(command, type)
|
||||
.then((values) => {
|
||||
setLoading(false);
|
||||
if (values && values instanceof Array && values.length > 0) {
|
||||
const uniqValues = Array.from(new Set([...selected, ...values]));
|
||||
setSelected(uniqValues);
|
||||
sendUpdate(uniqValues);
|
||||
setInputValue('');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
},
|
||||
[selected]
|
||||
);
|
||||
|
||||
/**
|
||||
* Check if the input is disabled
|
||||
@@ -268,10 +286,13 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
|
||||
const inputPlaceholder = useMemo((): string => {
|
||||
if (checkIsDisabled()) {
|
||||
return l10n.t(LocalizationKey.panelTagPickerInputPlaceholderDisabled, `${limit} ${label || type.toLowerCase()}`);
|
||||
return l10n.t(
|
||||
LocalizationKey.panelTagPickerInputPlaceholderDisabled,
|
||||
`${limit} ${label || type.toLowerCase()}`
|
||||
);
|
||||
}
|
||||
|
||||
return l10n.t(LocalizationKey.panelTagPickerInputPlaceholderEmpty, (label || type.toLowerCase()));
|
||||
return l10n.t(LocalizationKey.panelTagPickerInputPlaceholderEmpty, label || type.toLowerCase());
|
||||
}, [label, type, checkIsDisabled]);
|
||||
|
||||
const showRequiredState = useMemo(() => {
|
||||
@@ -279,25 +300,44 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
}, [required, selected]);
|
||||
|
||||
const actionElement = useMemo(() => {
|
||||
if (!settings?.aiEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type !== TagType.tags && type !== TagType.categories) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className='metadata_field__title__action'
|
||||
title={l10n.t(LocalizationKey.panelTagPickerAiSuggest, (label?.toLowerCase() || type.toLowerCase()))}
|
||||
type='button'
|
||||
onClick={() => suggestTaxonomy(type)}
|
||||
disabled={loading}>
|
||||
<SparklesIcon />
|
||||
</button>
|
||||
<div className="flex gap-4">
|
||||
{settings?.aiEnabled && (
|
||||
<button
|
||||
className="metadata_field__title__action"
|
||||
title={l10n.t(
|
||||
LocalizationKey.panelTagPickerAiSuggest,
|
||||
label?.toLowerCase() || type.toLowerCase()
|
||||
)}
|
||||
type="button"
|
||||
onClick={() => suggestTaxonomy('ai', type)}
|
||||
disabled={loading}
|
||||
>
|
||||
<SparklesIcon />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{settings?.copilotEnabled && (
|
||||
<button
|
||||
className="metadata_field__title__action"
|
||||
title={l10n.t(
|
||||
LocalizationKey.panelTagPickerCopilotSuggest,
|
||||
label?.toLowerCase() || type.toLowerCase()
|
||||
)}
|
||||
type="button"
|
||||
onClick={() => suggestTaxonomy('copilot', type)}
|
||||
disabled={loading}
|
||||
>
|
||||
<CopilotIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [settings?.aiEnabled, label, type]);
|
||||
}, [settings?.aiEnabled, settings?.copilotEnabled, label, type]);
|
||||
|
||||
const sortedSelectedTags = useMemo(() => {
|
||||
const safeSelected = selected || [];
|
||||
@@ -328,13 +368,6 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
|
||||
return (
|
||||
<div className={`article__tags metadata_field`}>
|
||||
{
|
||||
loading && (
|
||||
<div className='metadata_field__loading'>
|
||||
{l10n.t(LocalizationKey.panelTagPickerAiGenerating)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<FieldTitle
|
||||
label={
|
||||
<>
|
||||
@@ -342,7 +375,9 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
{limit !== undefined && limit > 0 ? (
|
||||
<>
|
||||
{` `}
|
||||
<span style={{ fontWeight: 'lighter' }}>({l10n.t(LocalizationKey.panelTagPickerLimit, limit)})</span>
|
||||
<span style={{ fontWeight: 'lighter' }}>
|
||||
({l10n.t(LocalizationKey.panelTagPickerLimit, limit)})
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
``
|
||||
@@ -354,79 +389,86 @@ const TagPicker: React.FunctionComponent<ITagPickerProps> = ({
|
||||
required={required}
|
||||
/>
|
||||
|
||||
<Downshift
|
||||
ref={dsRef}
|
||||
onChange={(selected) => onSelect(selected || '')}
|
||||
itemToString={(item) => (item ? item : '')}
|
||||
inputValue={inputValue}
|
||||
onInputValueChange={(value) => setInputValue(value)}
|
||||
>
|
||||
{({
|
||||
getInputProps,
|
||||
getItemProps,
|
||||
getMenuProps,
|
||||
isOpen,
|
||||
inputValue,
|
||||
getRootProps,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
clearSelection,
|
||||
highlightedIndex
|
||||
}) => (
|
||||
<>
|
||||
<div
|
||||
{...getRootProps(undefined, { suppressRefError: true })}
|
||||
className={`article__tags__input ${freeform ? 'freeform' : ''} ${showRequiredState ? 'required' : ''
|
||||
<div className="relative">
|
||||
{loading && (
|
||||
<div className="metadata_field__loading">
|
||||
{l10n.t(LocalizationKey.panelTagPickerAiGenerating)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Downshift
|
||||
ref={dsRef}
|
||||
onChange={(selected) => onSelect(selected || '')}
|
||||
itemToString={(item) => (item ? item : '')}
|
||||
inputValue={inputValue}
|
||||
onInputValueChange={(value) => setInputValue(value)}
|
||||
>
|
||||
{({
|
||||
getInputProps,
|
||||
getItemProps,
|
||||
getMenuProps,
|
||||
isOpen,
|
||||
inputValue,
|
||||
getRootProps,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
clearSelection,
|
||||
highlightedIndex
|
||||
}) => (
|
||||
<>
|
||||
<div
|
||||
{...getRootProps(undefined, { suppressRefError: true })}
|
||||
className={`article__tags__input ${freeform ? 'freeform' : ''} ${
|
||||
showRequiredState ? 'required' : ''
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
{...getInputProps({
|
||||
ref: inputRef,
|
||||
onFocus: openMenu as any,
|
||||
onClick: openMenu as any,
|
||||
onKeyDown: (e) => onEnterData(e, closeMenu, highlightedIndex),
|
||||
onBlur: () => {
|
||||
closeMenu();
|
||||
unsetFocus();
|
||||
if (!inputValue) {
|
||||
clearSelection();
|
||||
}
|
||||
},
|
||||
disabled: checkIsDisabled()
|
||||
})}
|
||||
placeholder={inputPlaceholder}
|
||||
/>
|
||||
>
|
||||
<input
|
||||
{...getInputProps({
|
||||
ref: inputRef,
|
||||
onFocus: openMenu as any,
|
||||
onClick: openMenu as any,
|
||||
onKeyDown: (e) => onEnterData(e, closeMenu, highlightedIndex),
|
||||
onBlur: () => {
|
||||
closeMenu();
|
||||
unsetFocus();
|
||||
if (!inputValue) {
|
||||
clearSelection();
|
||||
}
|
||||
},
|
||||
disabled: checkIsDisabled()
|
||||
})}
|
||||
placeholder={inputPlaceholder}
|
||||
/>
|
||||
|
||||
{freeform && (
|
||||
<button
|
||||
className={`article__tags__input__button`}
|
||||
title={l10n.t(LocalizationKey.panelTagPickerUnkown)}
|
||||
disabled={!inputValue || checkIsDisabled()}
|
||||
onClick={() => insertUnkownTag(closeMenu)}
|
||||
>
|
||||
<AddIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{freeform && (
|
||||
<button
|
||||
className={`article__tags__input__button`}
|
||||
title={l10n.t(LocalizationKey.panelTagPickerUnkown)}
|
||||
disabled={!inputValue || checkIsDisabled()}
|
||||
onClick={() => insertUnkownTag(closeMenu)}
|
||||
>
|
||||
<AddIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ul
|
||||
className={`field_dropdown article__tags__dropbox ${isOpen ? 'open' : 'closed'}`}
|
||||
style={{
|
||||
bottom: getDropdownStyle(isOpen)
|
||||
}}
|
||||
{...getMenuProps()}
|
||||
>
|
||||
{
|
||||
options
|
||||
<ul
|
||||
className={`field_dropdown article__tags__dropbox ${isOpen ? 'open' : 'closed'}`}
|
||||
style={{
|
||||
bottom: getDropdownStyle(isOpen)
|
||||
}}
|
||||
{...getMenuProps()}
|
||||
>
|
||||
{options
|
||||
.filter((option) => filterList(option, inputValue))
|
||||
.map((item, index) => (
|
||||
<li {...getItemProps({ key: item, index, item })}>{item}</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</Downshift>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</Downshift>
|
||||
</div>
|
||||
|
||||
<FieldMessage
|
||||
name={(label || type).toLowerCase()}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { CommandToCode } from '../../CommandToCode';
|
||||
import * as l10n from '@vscode/l10n';
|
||||
import { LocalizationKey } from '../../../localization';
|
||||
import { useDebounce } from '../../../hooks/useDebounce';
|
||||
import { CopilotIcon } from '../Icons';
|
||||
|
||||
const DEBOUNCE_TIME = 300;
|
||||
|
||||
@@ -91,39 +92,61 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
}
|
||||
}, [showRequiredState, isValid]);
|
||||
|
||||
const suggestDescription = () => {
|
||||
const suggestDescription = (type: 'ai' | 'copilot') => {
|
||||
setLoading(true);
|
||||
messageHandler.request<string>(CommandToCode.aiSuggestDescription).then((suggestion) => {
|
||||
setLoading(false);
|
||||
messageHandler
|
||||
.request<string>(
|
||||
type === 'copilot' ? CommandToCode.copilotSuggestDescription : CommandToCode.aiSuggestDescription
|
||||
)
|
||||
.then((suggestion) => {
|
||||
setLoading(false);
|
||||
|
||||
if (suggestion) {
|
||||
setText(suggestion);
|
||||
onChange(suggestion);
|
||||
}
|
||||
}).catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
if (suggestion) {
|
||||
setText(suggestion);
|
||||
onChange(suggestion);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const actionElement = useMemo(() => {
|
||||
if (!settings?.aiEnabled || settings.seo.descriptionField !== name) {
|
||||
if (settings.seo.descriptionField !== name) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className='metadata_field__title__action'
|
||||
title={l10n.t(LocalizationKey.panelFieldsTextFieldAiMessage, label?.toLowerCase())}
|
||||
type='button'
|
||||
onClick={() => suggestDescription()}
|
||||
disabled={loading}>
|
||||
<SparklesIcon />
|
||||
</button>
|
||||
<div className="flex gap-4">
|
||||
{settings?.aiEnabled && (
|
||||
<button
|
||||
className="metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50"
|
||||
title={l10n.t(LocalizationKey.panelFieldsTextFieldAiMessage, label?.toLowerCase())}
|
||||
type="button"
|
||||
onClick={() => suggestDescription('ai')}
|
||||
disabled={loading}
|
||||
>
|
||||
<SparklesIcon />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{settings?.copilotEnabled && (
|
||||
<button
|
||||
className="metadata_field__title__action inline-block text-[var(--vscode-editor-foreground)] disabled:opacity-50"
|
||||
title={l10n.t(LocalizationKey.panelFieldsTextFieldCopilotMessage, label?.toLowerCase())}
|
||||
type="button"
|
||||
onClick={() => suggestDescription('copilot')}
|
||||
disabled={loading}
|
||||
>
|
||||
<CopilotIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [settings?.aiEnabled, name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (text !== value && (lastUpdated === null || (Date.now() - DEBOUNCE_TIME) > lastUpdated)) {
|
||||
if (text !== value && (lastUpdated === null || Date.now() - DEBOUNCE_TIME > lastUpdated)) {
|
||||
setText(value || null);
|
||||
}
|
||||
setLastUpdated(null);
|
||||
@@ -137,42 +160,49 @@ export const TextField: React.FunctionComponent<ITextFieldProps> = ({
|
||||
|
||||
return (
|
||||
<div className={`metadata_field`}>
|
||||
{
|
||||
loading && (
|
||||
<div className='metadata_field__loading'>
|
||||
<FieldTitle
|
||||
label={label}
|
||||
actionElement={actionElement}
|
||||
icon={<PencilIcon />}
|
||||
required={required}
|
||||
/>
|
||||
|
||||
<div className='relative'>
|
||||
{loading && (
|
||||
<div className="metadata_field__loading">
|
||||
{l10n.t(LocalizationKey.panelFieldsTextFieldAiGenerate)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
<FieldTitle label={label} actionElement={actionElement} icon={<PencilIcon />} required={required} />
|
||||
|
||||
{wysiwyg ? (
|
||||
<React.Suspense fallback={<div>{l10n.t(LocalizationKey.panelFieldsTextFieldLoading)}</div>}>
|
||||
<WysiwygField text={text || ''} onChange={onTextChange} />
|
||||
</React.Suspense>
|
||||
) : singleLine ? (
|
||||
<input
|
||||
className={`metadata_field__input`}
|
||||
value={text || ''}
|
||||
onChange={(e) => onTextChange(e.currentTarget.value)}
|
||||
placeholder={placeholder}
|
||||
style={{
|
||||
border
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<textarea
|
||||
className={`metadata_field__textarea`}
|
||||
rows={rows || 2}
|
||||
value={text || ''}
|
||||
onChange={(e) => onTextChange(e.currentTarget.value)}
|
||||
placeholder={placeholder}
|
||||
style={{
|
||||
border
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{wysiwyg ? (
|
||||
<React.Suspense
|
||||
fallback={<div>{l10n.t(LocalizationKey.panelFieldsTextFieldLoading)}</div>}
|
||||
>
|
||||
<WysiwygField text={text || ''} onChange={onTextChange} />
|
||||
</React.Suspense>
|
||||
) : singleLine ? (
|
||||
<input
|
||||
className={`metadata_field__input`}
|
||||
value={text || ''}
|
||||
onChange={(e) => onTextChange(e.currentTarget.value)}
|
||||
placeholder={placeholder}
|
||||
style={{
|
||||
border
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<textarea
|
||||
className={`metadata_field__textarea`}
|
||||
rows={rows || 2}
|
||||
value={text || ''}
|
||||
onChange={(e) => onTextChange(e.currentTarget.value)}
|
||||
placeholder={placeholder}
|
||||
style={{
|
||||
border
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{limit && limit > 0 && (text || '').length > limit && (
|
||||
<div className={`metadata_field__limit`}>
|
||||
|
||||
@@ -211,7 +211,7 @@ export const WrapperField: React.FunctionComponent<IWrapperFieldProps> = ({
|
||||
singleLine={field.single}
|
||||
limit={limit}
|
||||
wysiwyg={field.wysiwyg}
|
||||
rows={3}
|
||||
rows={4}
|
||||
onChange={onFieldChange}
|
||||
value={(fieldValue as string) || null}
|
||||
required={!!field.required}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ICopilotIconProps { }
|
||||
|
||||
export const CopilotIcon: React.FunctionComponent<ICopilotIconProps> = (props: React.PropsWithChildren<ICopilotIconProps>) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 208">
|
||||
<path fill='currentcolor' d="M205.28 31.36c14.096 14.88 20.016 35.2 22.512 63.68c6.626 0 12.805 1.47 16.976 7.152l7.792 10.56A17.55 17.55 0 0 1 256 123.2v28.688c-.008 3.704-1.843 7.315-4.832 9.504C215.885 187.222 172.35 208 128 208c-49.066 0-98.19-28.273-123.168-46.608c-2.989-2.189-4.825-5.8-4.832-9.504V123.2c0-3.776 1.2-7.424 3.424-10.464l7.792-10.544c4.173-5.657 10.38-7.152 16.992-7.152c2.496-28.48 8.4-48.8 22.512-63.68C77.331 3.165 112.567.06 127.552 0H128c14.72 0 50.4 2.88 77.28 31.36m-77.264 47.376c-3.04 0-6.544.176-10.272.544c-1.312 4.896-3.248 9.312-6.08 12.128c-11.2 11.2-24.704 12.928-31.936 12.928c-6.802 0-13.927-1.42-19.744-5.088c-5.502 1.808-10.786 4.415-11.136 10.912c-.586 12.28-.637 24.55-.688 36.824c-.026 6.16-.05 12.322-.144 18.488c.024 3.579 2.182 6.903 5.44 8.384C79.936 185.92 104.976 192 128.016 192c23.008 0 48.048-6.08 74.512-18.144c3.258-1.48 5.415-4.805 5.44-8.384c.317-18.418.062-36.912-.816-55.312h.016c-.342-6.534-5.648-9.098-11.168-10.912c-5.82 3.652-12.927 5.088-19.728 5.088c-7.232 0-20.72-1.728-31.936-12.928c-2.832-2.816-4.768-7.232-6.08-12.128a106 106 0 0 0-10.24-.544m-26.941 43.93c5.748 0 10.408 4.66 10.408 10.409v19.183c0 5.749-4.66 10.409-10.408 10.409s-10.408-4.66-10.408-10.409v-19.183c0-5.748 4.66-10.408 10.408-10.408m53.333 0c5.749 0 10.409 4.66 10.409 10.409v19.183c0 5.749-4.66 10.409-10.409 10.409c-5.748 0-10.408-4.66-10.408-10.409v-19.183c0-5.748 4.66-10.408 10.408-10.408M81.44 28.32c-11.2 1.12-20.64 4.8-25.44 9.92c-10.4 11.36-8.16 40.16-2.24 46.24c4.32 4.32 12.48 7.2 21.28 7.2c6.72 0 19.52-1.44 30.08-12.16c4.64-4.48 7.52-15.68 7.2-27.04c-.32-9.12-2.88-16.64-6.72-19.84c-4.16-3.68-13.6-5.28-24.16-4.32m68.96 4.32c-3.84 3.2-6.4 10.72-6.72 19.84c-.32 11.36 2.56 22.56 7.2 27.04c10.56 10.72 23.36 12.16 30.08 12.16c8.8 0 16.96-2.88 21.28-7.2c5.92-6.08 8.16-34.88-2.24-46.24c-4.8-5.12-14.24-8.8-25.44-9.92c-10.56-.96-20 .64-24.16 4.32M128 56c-2.56 0-5.6.16-8.96.48c.32 1.76.48 3.68.64 5.76c0 1.44 0 2.88-.16 4.48c3.2-.32 5.92-.32 8.48-.32s5.28 0 8.48.32c-.16-1.6-.16-3.04-.16-4.48c.16-2.08.32-4 .64-5.76c-3.36-.32-6.4-.48-8.96-.48" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -3,6 +3,7 @@ export * from './ArchiveIcon';
|
||||
export * from './BranchIcon';
|
||||
export * from './BugIcon';
|
||||
export * from './CenterIcon';
|
||||
export * from './CopilotIcon';
|
||||
export * from './FileIcon';
|
||||
export * from './FolderOpenedIcon';
|
||||
export * from './FrontMatterIcon';
|
||||
|
||||
@@ -93,27 +93,29 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
id={`tags`}
|
||||
id={`metadata`}
|
||||
title={`${l10n.t(LocalizationKey.panelMetadataTitle)}${contentType?.name ? ` (${contentType?.name})` : ""}`}
|
||||
className={`inherit z-20`}>
|
||||
<FeatureFlag features={features || DEFAULT_PANEL_FEATURE_FLAGS} flag={FEATURE_FLAG.panel.contentType}>
|
||||
<ContentTypeValidator fields={contentType?.fields || []} metadata={metadata} />
|
||||
</FeatureFlag>
|
||||
|
||||
{
|
||||
metadata.fmError && metadata.fmErrorMessage ? (
|
||||
<div className={`space-y-4`}>
|
||||
<p className={`text-[var(--vscode-errorForeground)]`}>{metadata.fmError}</p>
|
||||
<div className='metadata_fields space-y-6'>
|
||||
{
|
||||
metadata.fmError && metadata.fmErrorMessage ? (
|
||||
<div className={`space-y-4`}>
|
||||
<p className={`text-[var(--vscode-errorForeground)]`}>{metadata.fmError}</p>
|
||||
|
||||
<button
|
||||
title={l10n.t(LocalizationKey.panelMetadataFocusProblems)}
|
||||
onClick={focusProblems}
|
||||
type={`button`}>
|
||||
{l10n.t(LocalizationKey.panelMetadataFocusProblems)}
|
||||
</button>
|
||||
</div>
|
||||
) : allFields
|
||||
}
|
||||
<button
|
||||
title={l10n.t(LocalizationKey.panelMetadataFocusProblems)}
|
||||
onClick={focusProblems}
|
||||
type={`button`}>
|
||||
{l10n.t(LocalizationKey.panelMetadataFocusProblems)}
|
||||
</button>
|
||||
</div>
|
||||
) : allFields
|
||||
}
|
||||
</div>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
|
||||
+23
-25
@@ -316,7 +316,6 @@ button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
&.metadata_field__alert {
|
||||
justify-content: flex-start;
|
||||
@@ -326,31 +325,30 @@ button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
all: unset;
|
||||
.metadata_field__title__action {
|
||||
all: unset;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
|
||||
&:hover {
|
||||
color: var(--vscode-button-hoverBackground);
|
||||
fill: var(--vscode-button-hoverBackground);
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.metadata_field__title__action {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
color: var(--vscode-disabledForeground);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--vscode-button-hoverBackground);
|
||||
fill: var(--vscode-button-hoverBackground);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
color: var(--vscode-disabledForeground);
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-right: 0;
|
||||
}
|
||||
svg {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,15 +362,15 @@ button {
|
||||
}
|
||||
|
||||
.metadata_field__loading {
|
||||
border-radius: 0.25rem;
|
||||
backdrop-filter: blur(15px);
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: calc(100% + 2.5em);
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
top: 30px;
|
||||
background-color: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
top: 0;
|
||||
left: -1.25rem;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
import {
|
||||
CancellationTokenSource,
|
||||
LanguageModelChatMessage,
|
||||
LanguageModelChatResponse,
|
||||
extensions,
|
||||
lm,
|
||||
version as VscodeVersion
|
||||
} from 'vscode';
|
||||
import { Logger, Settings, TaxonomyHelper } from '../helpers';
|
||||
import { SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH } from '../constants';
|
||||
import { TagType } from '../panelWebView/TagType';
|
||||
import { TaxonomyType } from '../models';
|
||||
|
||||
export class Copilot {
|
||||
private static personality =
|
||||
'You are a CMS expert for Front Matter CMS and your task is to assist the user to help generate content for their article.';
|
||||
|
||||
/**
|
||||
* Checks if the GitHub Copilot extension is installed.
|
||||
* @returns A promise that resolves to a boolean indicating whether the extension is installed.
|
||||
*/
|
||||
public static async isInstalled(): Promise<boolean> {
|
||||
if (!VscodeVersion.includes('insider')) {
|
||||
// At the moment Copilot is only available in the insider version of VS Code
|
||||
return false;
|
||||
}
|
||||
|
||||
const copilotExt = extensions.getExtension(`GitHub.copilot`);
|
||||
return !!copilotExt;
|
||||
}
|
||||
|
||||
public static async suggestTitles(title: string): Promise<string[] | undefined> {
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chars = Settings.get<number>(SETTING_SEO_TITLE_LENGTH) || 60;
|
||||
const messages = [
|
||||
LanguageModelChatMessage.User(Copilot.personality),
|
||||
LanguageModelChatMessage.User(
|
||||
`The user wants you to create a SEO friendly title. You should give the user a couple of suggestions based on the provided title.
|
||||
|
||||
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.`
|
||||
),
|
||||
LanguageModelChatMessage.User(`The title of the blog post is """${title}""".`)
|
||||
];
|
||||
|
||||
const chatResponse = await this.getChatResponse(messages);
|
||||
if (!chatResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
let titles = chatResponse.split('\n').map((title) => title.trim());
|
||||
// Remove 1. or - from the beginning of the title
|
||||
titles = titles.map((title) => title.replace(/^\d+\.\s+|-/, '').trim());
|
||||
// Only take the titles wrapped in quotes
|
||||
titles = titles.filter((title) => title.startsWith('"') && title.endsWith('"'));
|
||||
// Remove the quotes from the beginning and end of the title
|
||||
titles = titles.map((title) => title.slice(1, -1));
|
||||
return titles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a SEO friendly abstract/description for an article based on the provided title and content.
|
||||
*
|
||||
* @param title - The title of the blog post.
|
||||
* @param content - The content of the blog post.
|
||||
* @returns A chat response containing the generated description.
|
||||
*/
|
||||
public static async suggestDescription(title: string, content?: string) {
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chars = Settings.get<number>(SETTING_SEO_DESCRIPTION_LENGTH) || 160;
|
||||
const messages = [
|
||||
LanguageModelChatMessage.User(Copilot.personality),
|
||||
LanguageModelChatMessage.User(
|
||||
`The user wants you to create a SEO friendly abstract/description. When the user provides a title and/or content, you should use this information to generate the description.
|
||||
|
||||
IMPORTANT: You are only allowed to respond with a text that should not exceed ${chars} characters in length.`
|
||||
),
|
||||
LanguageModelChatMessage.User(`The title of the blog post is """${title}""".`)
|
||||
];
|
||||
|
||||
if (content) {
|
||||
messages.push(
|
||||
LanguageModelChatMessage.User(`The content of the blog post is: """${content}""".`)
|
||||
);
|
||||
}
|
||||
|
||||
const chatResponse = await this.getChatResponse(messages);
|
||||
return chatResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggests taxonomy tags based on the provided title, tag type, description, and content.
|
||||
*
|
||||
* @param title - The title of the blog post.
|
||||
* @param tagType - The type of taxonomy tags to suggest (Tag or Category).
|
||||
* @param description - The description of the blog post (optional).
|
||||
* @param content - The content of the blog post (optional).
|
||||
* @returns A promise that resolves to an array of suggested taxonomy tags, or undefined if no title is provided.
|
||||
*/
|
||||
public static async suggestTaxonomy(
|
||||
title: string,
|
||||
tagType: TagType,
|
||||
description?: string,
|
||||
content?: string
|
||||
): Promise<string[] | undefined> {
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = [
|
||||
LanguageModelChatMessage.User(Copilot.personality),
|
||||
LanguageModelChatMessage.User(
|
||||
`The user wants you to suggest some taxonomy tags. When the user provides a title, description, list of available taxonomy tags, and/or content, you should use this information to generate the tags.
|
||||
|
||||
IMPORTANT: You are only allowed to respond with a list of tags separated by commas. Example: tag1, tag2, tag3.`
|
||||
),
|
||||
LanguageModelChatMessage.User(`The title of the blog post is """${title}""".`)
|
||||
];
|
||||
|
||||
if (description) {
|
||||
messages.push(
|
||||
LanguageModelChatMessage.User(`The description of the blog post is: """${description}""".`)
|
||||
);
|
||||
}
|
||||
|
||||
let options =
|
||||
tagType === TagType.tags
|
||||
? await TaxonomyHelper.get(TaxonomyType.Tag)
|
||||
: await TaxonomyHelper.get(TaxonomyType.Category);
|
||||
const optionsString = options?.join(',') || '';
|
||||
|
||||
if (optionsString) {
|
||||
messages.push(
|
||||
LanguageModelChatMessage.User(
|
||||
`The available taxonomy tags are: ${optionsString}. Please select the tags that are relevant to the article. You are allowed to suggest a maximum of 5 tags and suggest new tags if necessary.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (content) {
|
||||
messages.push(
|
||||
LanguageModelChatMessage.User(`The content of the blog post is: """${content}""".`)
|
||||
);
|
||||
}
|
||||
|
||||
const chatResponse = await this.getChatResponse(messages);
|
||||
|
||||
if (!chatResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the chat response contains a colon character, we take the text after the colon as the response.
|
||||
if (chatResponse.includes(':')) {
|
||||
return chatResponse
|
||||
.split(':')[1]
|
||||
.split(',')
|
||||
.map((tag) => tag.trim());
|
||||
}
|
||||
|
||||
// Otherwise, we split the response by commas.
|
||||
return chatResponse.split(',').map((tag) => tag.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the chat response from the language model.
|
||||
* @param messages - The chat messages to send to the language model.
|
||||
* @returns The concatenated text fragments from the chat response.
|
||||
*/
|
||||
private static async getChatResponse(messages: LanguageModelChatMessage[]) {
|
||||
let chatResponse: LanguageModelChatResponse | undefined;
|
||||
|
||||
try {
|
||||
const model = await this.getModel();
|
||||
chatResponse = await model.sendRequest(messages, {}, new CancellationTokenSource().token);
|
||||
} catch (err) {
|
||||
Logger.error(`Copilot:getChatResponse:: ${(err as Error).message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let allFragments = [];
|
||||
for await (const fragment of chatResponse.text) {
|
||||
allFragments.push(fragment);
|
||||
}
|
||||
|
||||
return allFragments.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the chat model for the Copilot service.
|
||||
* @returns A Promise that resolves to the chat model.
|
||||
*/
|
||||
private static async getModel() {
|
||||
const [model] = await lm.selectChatModels({
|
||||
vendor: 'copilot'
|
||||
// family: 'gpt-4'
|
||||
});
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user