Skip to content

Commit 576c4b3

Browse files
feat: allow to customize api endpoint and model (#31)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent a35b170 commit 576c4b3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3615
-2151
lines changed

.github/workflows/ci.yml

+18
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,24 @@ jobs:
2727
- name: Lint files
2828
run: pnpm run lint
2929

30+
test:
31+
name: Test
32+
runs-on: ubuntu-latest
33+
34+
steps:
35+
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
36+
- run: corepack enable
37+
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
38+
with:
39+
node-version: 20.5
40+
cache: pnpm
41+
42+
- name: Install dependencies
43+
run: pnpm install
44+
45+
- name: Test
46+
run: pnpm run test
47+
3048
build:
3149
name: Build
3250
runs-on: ubuntu-latest

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ out
44
.vscode-test/
55
*.vsix
66
example.md
7+
coverage
8+
build

.vscode/launch.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"outFiles": [
1313
"${workspaceFolder}/packages/utils-ai-vscode/dist/**/*.js"
1414
],
15-
"preLaunchTask": "npm: ext:dev"
15+
"preLaunchTask": "npm: dev"
1616
}
1717
]
1818
}

.vscode/tasks.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
"version": "2.0.0",
55
"tasks": [
66
{
7+
"path": "packages/utils-ai-vscode",
78
"type": "npm",
8-
"script": "ext:dev",
9+
"script": "dev",
910
"isBackground": true,
1011
"presentation": {
1112
"reveal": "never"

package.json

+1-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
"lint": "eslint .",
1212
"lint:fix": "eslint . --fix",
1313
"build": "pnpm run --filter utils-ai build && pnpm run --filter utils-ai-vscode build",
14-
"build:stub": "pnpm run -r build:stub",
15-
"postinstall": "pnpm run build:stub",
16-
"ext:dev": "pnpm run --filter utils-ai build && pnpm run --filter utils-ai-vscode dev"
14+
"test": "pnpm run -r test"
1715
},
1816
"devDependencies": {
1917
"@antfu/eslint-config": "1.0.0-beta.28",

packages/utils-ai-vscode/package.json

+136-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"publisher": "barbapapazes",
33
"name": "utils-ai-vscode",
44
"displayName": "Utils AI",
5+
"type": "module",
56
"version": "0.3.1",
67
"private": true,
78
"description": "Write faster with AI",
@@ -14,22 +15,146 @@
1415
"categories": [
1516
"Other"
1617
],
17-
"main": "./dist/index.js",
18+
"main": "./dist/index.cjs",
1819
"icon": "./images/icon.png",
1920
"engines": {
2021
"vscode": "^1.83.1"
2122
},
22-
"activationEvents": [],
23+
"activationEvents": [
24+
"onStartupFinished"
25+
],
2326
"contributes": {
27+
"configuration": {
28+
"title": "Utils AI",
29+
"properties": {
30+
"utilsAi.endpoint": {
31+
"type": "string",
32+
"default": "https://api.openai.com/v1/chat/completions",
33+
"markdownDescription": "The endpoint of the AI server. **Must be compatible with the OpenAI completions API**"
34+
},
35+
"utilsAi.model": {
36+
"type": "string",
37+
"default": "gpt-3.5-turbo",
38+
"markdownDescription": "The model to use for the AI. The model is passed to the AI server using the body."
39+
},
40+
"utilsAi.contextWindow": {
41+
"type": "number",
42+
"default": 16385,
43+
"markdownDescription": "Context window of the token. This is used to know how to split the text into chunks to allow large text to be processed."
44+
},
45+
"utilsAi.outputTokens": {
46+
"type": "number",
47+
"default": 4096,
48+
"markdownDescription": "Maximal tokens that can be generated by the AI. Smaller values will be faster but could cut the text for a correction."
49+
},
50+
"utilsAi.preferredLanguage": {
51+
"type": "string",
52+
"default": "en",
53+
"enum": [
54+
"en",
55+
"fr"
56+
],
57+
"enumDescriptions": [
58+
"Select English Prompt",
59+
"Select French Prompt"
60+
]
61+
},
62+
"utilsAi.alwaysAskLanguage": {
63+
"type": "boolean",
64+
"default": false,
65+
"markdownDescription": "Always ask for the language before doing an action."
66+
},
67+
"utilsAi.prompts": {
68+
"type": "object",
69+
"properties": {
70+
"fr": {
71+
"type": "object",
72+
"properties": {
73+
"spell-checker": {
74+
"type": "object",
75+
"properties": {
76+
"name": {
77+
"type": "string",
78+
"markdownDescription": "Prompt to use for the name of the spell checker."
79+
},
80+
"message": {
81+
"type": "string",
82+
"markdownDescription": "Prompt to use for the text of the spell checker."
83+
}
84+
}
85+
},
86+
"descriptor": {
87+
"type": "object",
88+
"properties": {
89+
"name": {
90+
"type": "string",
91+
"markdownDescription": "Prompt to use for the name of the descriptor."
92+
},
93+
"message": {
94+
"type": "string",
95+
"markdownDescription": "Prompt to use for the text of the spell descriptor."
96+
}
97+
}
98+
}
99+
}
100+
},
101+
"en": {
102+
"type": "object",
103+
"properties": {
104+
"spell-checker": {
105+
"type": "object",
106+
"properties": {
107+
"name": {
108+
"type": "string",
109+
"markdownDescription": "Prompt to use for the name of the spell checker."
110+
},
111+
"message": {
112+
"type": "string",
113+
"markdownDescription": "Prompt to use for the text of the spell checker."
114+
}
115+
}
116+
},
117+
"descriptor": {
118+
"type": "object",
119+
"properties": {
120+
"name": {
121+
"type": "string",
122+
"markdownDescription": "Prompt to use for the name of the descriptor."
123+
},
124+
"message": {
125+
"type": "string",
126+
"markdownDescription": "Prompt to use for the text of the spell descriptor."
127+
}
128+
}
129+
}
130+
}
131+
},
132+
"markdownDescription": "Prompts to use for the AI. The key is the language and the value is the prompt."
133+
}
134+
},
135+
"utilsAi.supportedExtensions": {
136+
"definitions": "Supported extensions to allow the commands to be shown.",
137+
"type": "array",
138+
"items": {
139+
"type": "string"
140+
},
141+
"default": [
142+
".txt",
143+
".md",
144+
".mdx"
145+
]
146+
}
147+
}
148+
},
24149
"commands": [
25150
{
26-
"command": "barbapapazes.utils-ai-vscode.addOpenAIKey",
27-
"title": "Add OpenAI API Key",
151+
"command": "barbapapazes.utils-ai-vscode.addAuthToken",
152+
"title": "Add an authorization token",
28153
"category": "Utils AI"
29154
},
30155
{
31-
"command": "barbapapazes.utils-ai-vscode.removeOpenAIKey",
32-
"title": "Remove OpenAI API Key",
156+
"command": "barbapapazes.utils-ai-vscode.deleteAuthToken",
157+
"title": "Remove the authorization token",
33158
"category": "Utils AI"
34159
},
35160
{
@@ -51,18 +176,18 @@
51176
"commandPalette": [
52177
{
53178
"command": "barbapapazes.utils-ai-vscode.correct",
54-
"when": "editorLangId == 'markdown'"
179+
"when": "resourceExtname in barbapapazes.utils-ai-vscode.supportedExtensions"
55180
},
56181
{
57182
"command": "barbapapazes.utils-ai-vscode.description",
58-
"when": "editorLangId == 'markdown'"
183+
"when": "resourceExtname in barbapapazes.utils-ai-vscode.supportedExtensions"
59184
}
60185
],
61186
"editor/title": [
62187
{
63188
"command": "barbapapazes.utils-ai-vscode.correct",
64189
"group": "navigation",
65-
"when": "editorLangId == 'markdown'"
190+
"when": "resourceExtname in barbapapazes.utils-ai-vscode.supportedExtensions"
66191
}
67192
]
68193
}
@@ -74,9 +199,11 @@
74199
"publish": "vsce publish --no-dependencies"
75200
},
76201
"dependencies": {
202+
"defu": "^6.1.2",
77203
"utils-ai": "workspace:*"
78204
},
79205
"devDependencies": {
206+
"@poppinss/utils": "^6.7.3",
80207
"@types/node": "^18.18.6",
81208
"@types/vscode": "^1.83.1",
82209
"@vscode/vsce": "^2.21.1",

packages/utils-ai-vscode/src/commands/correct.ts packages/utils-ai-vscode/src/commands/correct_command.ts

+63-9
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,55 @@
11
import * as vscode from 'vscode'
2-
import { correct, getPrompt } from 'utils-ai'
3-
import { getOpenAIKey } from '../secrets'
2+
import type { Language } from 'utils-ai'
3+
import { Correcter, CorrecterOptions, FetcherOptions, HttpFetcher, Prompter, PrompterOptions, SimpleMessagesFactory, SimpleSplitter, SimpleTokenizer } from 'utils-ai'
4+
import { Configurator } from '../configurator.js'
5+
import { Logger } from '../logger.js'
6+
import { SecretsStorage } from '../secrets_storage.js'
47

58
export function correctCommand(context: vscode.ExtensionContext) {
6-
const secrets = context.secrets
9+
const logger = new Logger(
10+
vscode.window,
11+
)
12+
13+
const secretsStorage = new SecretsStorage(
14+
context.secrets,
15+
)
716

817
return async () => {
9-
const accessKey = await getOpenAIKey(secrets)
18+
logger.log('Correct command called.')
19+
20+
const configurator = new Configurator(
21+
vscode.workspace,
22+
)
1023

11-
if (!accessKey) {
12-
vscode.window.showErrorMessage('No key provided. Please provide a key using the "Add OpenAI Key" command.')
24+
const authToken = await secretsStorage.getAuthToken()
25+
26+
if (!authToken) {
27+
const message = 'No authorization token provided. Please provide an authorization token using the "Add Auth Token" command.'
28+
logger.log(message)
29+
vscode.window.showErrorMessage(message)
1330
return
1431
}
1532

33+
const preferredLanguage = configurator.alwaysAskLanguage
34+
? await vscode.window.showQuickPick(Prompter.LANGUAGES) as Language | undefined || configurator.preferredLanguage
35+
: configurator.preferredLanguage
36+
1637
const time = Date.now()
1738
const editor = vscode.window.activeTextEditor
1839

1940
if (editor) {
2041
const editorFilename = editor.document.fileName // Full path to the file
42+
logger.log(`File used: ${editorFilename}`)
2143
const filename = editorFilename.split('/').pop()
2244

2345
// Commit current file to git before correcting. This is useful for tracking changes.
2446
const gitExtension = vscode.extensions.getExtension('vscode.git')?.exports
2547
const isActivated = gitExtension.enabled
2648

2749
if (!isActivated) {
28-
vscode.window.showErrorMessage('Git is not activated. Please activate Git in VSCode.')
50+
const message = 'Git is not activated. Please activate Git in VSCode.'
51+
logger.log(message)
52+
vscode.window.showErrorMessage(message)
2953
return
3054
}
3155

@@ -35,6 +59,7 @@ export function correctCommand(context: vscode.ExtensionContext) {
3559
if (git.repositories.length > 0) {
3660
const repository = git.repositories[0]
3761

62+
logger.log(`Committing ${filename}...`)
3863
vscode.window.showInformationMessage(`Committing ${filename}...`)
3964

4065
// Save to be sure to commit the latest changes
@@ -45,12 +70,18 @@ export function correctCommand(context: vscode.ExtensionContext) {
4570
await repository.commit(`chore: save ${filename} before correcting`)
4671
}
4772
catch (error: any) {
73+
logger.log('No changes to commit.')
4874
// Ignore error because it's mean that the file is already committed
4975
}
5076
}
5177
}
5278

53-
const prompt = getPrompt('spell-checker-md', 'en')
79+
const prompterOptions = new PrompterOptions(
80+
preferredLanguage,
81+
)
82+
const prompter = new Prompter(prompterOptions)
83+
prompter.merge(configurator.prompts)
84+
const prompt = prompter.find('spell-checker')
5485

5586
const hasSelection = !editor.selection.isEmpty
5687
const selection = editor.selection
@@ -69,7 +100,28 @@ export function correctCommand(context: vscode.ExtensionContext) {
69100
title,
70101
cancellable: false,
71102
}, async () => {
72-
const correctedText = await correct(text, prompt.message, { ai: { accessKey } })
103+
const messagesFactory = new SimpleMessagesFactory()
104+
105+
const tokenizer = new SimpleTokenizer()
106+
const splitter = new SimpleSplitter(tokenizer)
107+
108+
const fetcherOptions = new FetcherOptions(
109+
authToken,
110+
configurator.endpoint,
111+
configurator.model,
112+
)
113+
const fetcher = new HttpFetcher(fetcherOptions)
114+
115+
const correcterOption = new CorrecterOptions(
116+
prompt.message,
117+
// Smaller than the context window.
118+
configurator.outputTokens,
119+
)
120+
const corrector = new Correcter(messagesFactory, tokenizer, splitter, fetcher, correcterOption)
121+
122+
logger.log(`Correcting ${hasSelection ? 'selection' : 'selection'} with prompt: ${prompt.message}`)
123+
logger.log(`Output tokens: ${configurator.outputTokens}`)
124+
const correctedText = await corrector.execute(text)
73125

74126
editor.edit((editBuilder) => {
75127
const range: vscode.Range = hasSelection ? selection : new vscode.Range(0, 0, text.length, 0)
@@ -86,9 +138,11 @@ export function correctCommand(context: vscode.ExtensionContext) {
86138
vscode.window.showInformationMessage(message, {
87139
detail: `Done in ${duration}ms.`,
88140
})
141+
logger.log(`Correction done in ${duration}ms.`)
89142
})
90143
}
91144
catch (error: any) {
145+
logger.log(error.message)
92146
vscode.window.showErrorMessage(error.message)
93147
}
94148
}

0 commit comments

Comments
 (0)