mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 08:52:42 +00:00
refactor(airi-plugin-vscode): better structure & format
This commit is contained in:
Vendored
+1
-1
@@ -34,7 +34,7 @@
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/plugins/airi-plugin-vscode"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/plugins/airi-plugin-vscode/dist/**/*.js"
|
||||
"${workspaceFolder}/plugins/airi-plugin-vscode/dist/**/*.cjs"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
"electron-updater": "^6.6.2",
|
||||
"es-toolkit": "^1.43.0",
|
||||
"h3": "2.0.1-rc.5",
|
||||
"injeca": "^0.1.5",
|
||||
"injeca": "catalog:",
|
||||
"jszip": "^3.10.1",
|
||||
"localforage": "^1.10.0",
|
||||
"mediabunny": "^1.27.2",
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import type { DependencyMap, ProvidedKey } from 'injeca'
|
||||
import type { Lifecycle, ProvidedBy } from 'injeca'
|
||||
import type * as vscode from 'vscode'
|
||||
|
||||
import { initLogger, LoggerFormat, LoggerLevel, useLogger } from '@guiiai/logg'
|
||||
import { noop } from 'es-toolkit'
|
||||
import { injeca } from 'injeca'
|
||||
import { injeca, lifecycle } from 'injeca'
|
||||
import { commands, window, workspace } from 'vscode'
|
||||
|
||||
import { Client } from './airi'
|
||||
import { ContextCollector } from './context-collector'
|
||||
|
||||
let updateTimer: NodeJS.Timeout | null = null
|
||||
let eventListeners: vscode.Disposable[] = []
|
||||
interface IntervalHandle {
|
||||
clearInterval: () => void
|
||||
setInterval: (fn: () => void) => NodeJS.Timeout
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the plugin
|
||||
@@ -30,9 +32,25 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const vscodeContext = injeca.provide('vscode:context', () => context)
|
||||
const client = injeca.provide('proj-airi:client', () => new Client())
|
||||
const contextCollector = injeca.provide('self:context-collector', () => new ContextCollector(contextLines))
|
||||
const eventListeners = injeca.provide('self:event-listeners', () => [] as vscode.Disposable[])
|
||||
const controlLoopInterval = injeca.provide('self:control-loop:interval:send', () => {
|
||||
let intervalTimer: NodeJS.Timeout | null = null
|
||||
|
||||
return {
|
||||
clearInterval: () => {
|
||||
if (intervalTimer) {
|
||||
clearInterval(intervalTimer)
|
||||
}
|
||||
},
|
||||
setInterval: (fn: () => void) => {
|
||||
intervalTimer = setInterval(fn, sendInterval)
|
||||
return intervalTimer
|
||||
},
|
||||
} satisfies IntervalHandle
|
||||
})
|
||||
|
||||
const extension = injeca.provide('extension', {
|
||||
dependsOn: { client, vscodeContext, contextCollector },
|
||||
dependsOn: { client, vscodeContext, contextCollector, eventListeners, lifecycle, controlLoopInterval },
|
||||
build: ({ dependsOn }) => setup({ ...dependsOn, isEnabled, sendInterval }),
|
||||
})
|
||||
|
||||
@@ -48,6 +66,9 @@ async function setup(params: {
|
||||
client: Client
|
||||
vscodeContext: vscode.ExtensionContext
|
||||
contextCollector: ContextCollector
|
||||
eventListeners: vscode.Disposable[]
|
||||
lifecycle: Lifecycle
|
||||
controlLoopInterval: IntervalHandle
|
||||
isEnabled: boolean
|
||||
sendInterval: number
|
||||
}) {
|
||||
@@ -67,13 +88,13 @@ async function setup(params: {
|
||||
commands.registerCommand('airi-vscode.enable', async () => {
|
||||
params.isEnabled = true
|
||||
await params.client.connect()
|
||||
await registerListeners({ sendInterval: params.sendInterval, contextCollector: params.contextCollector, client: params.client, isEnabled: params.isEnabled })
|
||||
await registerListeners({ ...params })
|
||||
window.showInformationMessage('AIRI enabled!')
|
||||
}),
|
||||
|
||||
commands.registerCommand('airi-vscode.disable', () => {
|
||||
params.isEnabled = false
|
||||
unregisterListeners()
|
||||
unregisterListeners({ eventListeners: params.eventListeners, controlLoopInterval: params.controlLoopInterval })
|
||||
params.client.disconnect()
|
||||
window.showInformationMessage('AIRI disabled!')
|
||||
}),
|
||||
@@ -86,7 +107,7 @@ async function setup(params: {
|
||||
|
||||
// Register event listeners if enabled
|
||||
if (params.isEnabled) {
|
||||
await registerListeners({ sendInterval: params.sendInterval, contextCollector: params.contextCollector, client: params.client, isEnabled: params.isEnabled })
|
||||
await registerListeners({ ...params })
|
||||
}
|
||||
|
||||
useLogger().log('AIRI activated successfully')
|
||||
@@ -95,11 +116,19 @@ async function setup(params: {
|
||||
/**
|
||||
* Register event listeners for file save and editor switch
|
||||
*/
|
||||
async function registerListeners(params: { contextCollector: ContextCollector, client: Client, isEnabled: boolean, sendInterval: number }) {
|
||||
unregisterListeners()
|
||||
async function registerListeners(params: {
|
||||
contextCollector: ContextCollector
|
||||
lifecycle: Lifecycle
|
||||
eventListeners: vscode.Disposable[]
|
||||
client: Client
|
||||
controlLoopInterval: IntervalHandle
|
||||
isEnabled: boolean
|
||||
sendInterval: number
|
||||
}) {
|
||||
unregisterListeners({ eventListeners: params.eventListeners, controlLoopInterval: params.controlLoopInterval })
|
||||
|
||||
// File save event
|
||||
eventListeners.push(
|
||||
params.eventListeners.push(
|
||||
workspace.onDidSaveTextDocument(async (document) => {
|
||||
const editor = window.activeTextEditor
|
||||
if (editor && editor.document === document) {
|
||||
@@ -107,13 +136,19 @@ async function registerListeners(params: { contextCollector: ContextCollector, c
|
||||
if (!ctx)
|
||||
return
|
||||
|
||||
params.client.replaceContext(JSON.stringify({ type: 'coding:save', data: ctx }))
|
||||
params.client.replaceContext(''
|
||||
+ `User saved the file: ${ctx.file.fileName} (located at ${ctx.file.path}). Here is the context around the cursor after saving:\n`
|
||||
+ '\n'
|
||||
+ `${ctx.context.before.join('\n')}\n`
|
||||
+ `${ctx.currentLine.text}\n`
|
||||
+ `${ctx.context.after.join('\n')}`,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
// Switch file event
|
||||
eventListeners.push(
|
||||
params.eventListeners.push(
|
||||
window.onDidChangeActiveTextEditor(async (editor) => {
|
||||
if (!editor) {
|
||||
return
|
||||
@@ -124,32 +159,45 @@ async function registerListeners(params: { contextCollector: ContextCollector, c
|
||||
return
|
||||
}
|
||||
|
||||
params.client.replaceContext(JSON.stringify({ type: 'coding:switch-file', data: ctx }))
|
||||
params.client.replaceContext(''
|
||||
+ `User switched to file: ${ctx.file.fileName} (located at ${ctx.file.path}). Here is the context around the cursor after switching:\n`
|
||||
+ '\n'
|
||||
+ `${ctx.context.before.join('\n')}\n`
|
||||
+ `${ctx.currentLine.text}\n`
|
||||
+ `${ctx.context.after.join('\n')}`,
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
// Start periodic monitoring if interval is set
|
||||
if (params.sendInterval > 0) {
|
||||
startMonitoring({ contextCollector: params.contextCollector, client: params.client, isEnabled: params.isEnabled, interval: params.sendInterval })
|
||||
startMonitoring({ ...params })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister all event listeners
|
||||
*/
|
||||
function unregisterListeners() {
|
||||
eventListeners.forEach(listener => listener.dispose())
|
||||
eventListeners = []
|
||||
stopMonitoring()
|
||||
function unregisterListeners(params: { eventListeners: vscode.Disposable[], controlLoopInterval: IntervalHandle }) {
|
||||
params.eventListeners.forEach(listener => listener.dispose())
|
||||
params.eventListeners = []
|
||||
stopMonitoring({ controlLoopInterval: params.controlLoopInterval })
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring the coding context
|
||||
*/
|
||||
function startMonitoring(params: { contextCollector: ContextCollector, client: Client, isEnabled: boolean, interval: number }) {
|
||||
stopMonitoring()
|
||||
function startMonitoring(params: {
|
||||
contextCollector: ContextCollector
|
||||
lifecycle: Lifecycle
|
||||
client: Client
|
||||
controlLoopInterval: IntervalHandle
|
||||
isEnabled: boolean
|
||||
sendInterval: number
|
||||
}) {
|
||||
stopMonitoring({ controlLoopInterval: params.controlLoopInterval })
|
||||
|
||||
updateTimer = setInterval(async () => {
|
||||
params.controlLoopInterval.setInterval(async () => {
|
||||
if (!params.isEnabled)
|
||||
return
|
||||
|
||||
@@ -161,27 +209,34 @@ function startMonitoring(params: { contextCollector: ContextCollector, client: C
|
||||
if (!ctx)
|
||||
return
|
||||
|
||||
params.client.replaceContext(JSON.stringify({ type: 'coding:context', data: ctx }))
|
||||
}, params.interval)
|
||||
params.client.replaceContext(''
|
||||
+ `User opened file is: ${ctx.file.fileName} (located at ${ctx.file.path}), and current cursor is at line ${ctx.cursor.line + 1}, character ${ctx.cursor.character + 1}.\n`
|
||||
+ '\n'
|
||||
+ `Here is the context around the cursor:\n`
|
||||
+ `\n`
|
||||
+ `${ctx.context.before.join('\n')}\n`
|
||||
+ `${ctx.currentLine.text}\n`
|
||||
+ `${ctx.context.after.join('\n')}`,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop monitoring
|
||||
*/
|
||||
function stopMonitoring() {
|
||||
if (updateTimer) {
|
||||
clearInterval(updateTimer)
|
||||
updateTimer = null
|
||||
}
|
||||
function stopMonitoring(params: { controlLoopInterval: IntervalHandle }) {
|
||||
params.controlLoopInterval.clearInterval()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate the plugin
|
||||
*/
|
||||
export async function deactivate() {
|
||||
const { client } = await injeca.resolve({ client: { key: 'proj-airi:client' } as ProvidedKey<'proj-airi:client', Client, DependencyMap | undefined> })
|
||||
const { client } = await injeca.resolve({ client: { key: 'proj-airi:client' } as unknown as ProvidedBy<Client> })
|
||||
const { eventListeners } = await injeca.resolve({ eventListeners: { key: 'self:event-listeners' } as unknown as ProvidedBy<vscode.Disposable[]> })
|
||||
const { controlLoopInterval } = await injeca.resolve({ controlLoopInterval: { key: 'self:control-loop:interval:send' } as unknown as ProvidedBy<IntervalHandle> })
|
||||
|
||||
unregisterListeners()
|
||||
unregisterListeners({ eventListeners, controlLoopInterval })
|
||||
client?.disconnect()
|
||||
useLogger().log('AIRI deactivated!')
|
||||
}
|
||||
|
||||
@@ -2,14 +2,29 @@
|
||||
* Coding context information
|
||||
*/
|
||||
export interface CodingContext {
|
||||
/** File information */
|
||||
/**
|
||||
* File information
|
||||
*
|
||||
* @example {
|
||||
* "path": "/home/neko/Git/github.com/moeru-ai/airi/package.json",
|
||||
* "languageId": "json",
|
||||
* "fileName": "/home/neko/Git/github.com/moeru-ai/airi/package.json"
|
||||
* }
|
||||
*/
|
||||
file: {
|
||||
path: string
|
||||
languageId: string
|
||||
fileName: string
|
||||
workspaceFolder?: string
|
||||
}
|
||||
/** Cursor position */
|
||||
/**
|
||||
* Cursor position
|
||||
*
|
||||
* {
|
||||
* "line": 5,
|
||||
* "character": 35
|
||||
* }
|
||||
*/
|
||||
cursor: {
|
||||
line: number
|
||||
character: number
|
||||
@@ -25,7 +40,26 @@ export interface CodingContext {
|
||||
lineNumber: number
|
||||
text: string
|
||||
}
|
||||
/** Context (previous and next N lines) */
|
||||
/**
|
||||
* Context (previous and next N lines)
|
||||
*
|
||||
* @example {
|
||||
* "before": [
|
||||
* "{",
|
||||
* " \"name\": \"@proj-airi/root\",",
|
||||
* " \"type\": \"module\",",
|
||||
* " \"version\": \"0.8.1-beta.12\",",
|
||||
* " \"private\": true,"
|
||||
* ],
|
||||
* "after": [
|
||||
* " \"description\": \"LLM powered virtual character\",",
|
||||
* " \"author\": {",
|
||||
* " \"name\": \"Moeru AI Project AIRI Team\",",
|
||||
* " \"email\": \"airi@moeru.ai\",",
|
||||
* " \"url\": \"https://github.com/moeru-ai\""
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
context: {
|
||||
before: string[]
|
||||
after: string[]
|
||||
@@ -35,7 +69,11 @@ export interface CodingContext {
|
||||
branch: string
|
||||
isDirty: boolean
|
||||
}
|
||||
/** Timestamp */
|
||||
/**
|
||||
* Timestamp
|
||||
*
|
||||
* @example 1768584314898
|
||||
*/
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
|
||||
Generated
+18
-12
@@ -127,8 +127,8 @@ catalogs:
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2
|
||||
injeca:
|
||||
specifier: ^0.1.5
|
||||
version: 0.1.5
|
||||
specifier: ^0.1.7
|
||||
version: 0.1.7
|
||||
is-network-error:
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0
|
||||
@@ -428,7 +428,7 @@ importers:
|
||||
version: 4.11.3
|
||||
injeca:
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.5(@guiiai/logg@1.2.11)(error-stack-parser@2.1.4)(nanoid@5.1.6)
|
||||
version: 0.1.7(@guiiai/logg@1.2.11)(error-stack-parser@2.1.4)(nanoid@5.1.6)
|
||||
postgres:
|
||||
specifier: ^3.4.7
|
||||
version: 3.4.7
|
||||
@@ -1011,8 +1011,8 @@ importers:
|
||||
specifier: 2.0.1-rc.5
|
||||
version: 2.0.1-rc.5(crossws@0.4.1(srvx@0.9.8(patch_hash=f0151386fdcbcb6f53833cf8f66926ef2eb31b71a2782d6e0960dec832ef108d)))
|
||||
injeca:
|
||||
specifier: ^0.1.5
|
||||
version: 0.1.5(@guiiai/logg@1.2.11)(error-stack-parser@2.1.4)(nanoid@5.1.6)
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.7(@guiiai/logg@1.2.11)(error-stack-parser@2.1.4)(nanoid@5.1.6)
|
||||
jszip:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1
|
||||
@@ -2969,7 +2969,7 @@ importers:
|
||||
version: 1.43.0
|
||||
injeca:
|
||||
specifier: 'catalog:'
|
||||
version: 0.1.5(@guiiai/logg@1.2.11)(error-stack-parser@2.1.4)(nanoid@5.1.6)
|
||||
version: 0.1.7(@guiiai/logg@1.2.11)(error-stack-parser@2.1.4)(nanoid@5.1.6)
|
||||
nanoid:
|
||||
specifier: 'catalog:'
|
||||
version: 5.1.6
|
||||
@@ -2991,7 +2991,7 @@ importers:
|
||||
version: 66.5.11
|
||||
'@wxt-dev/module-vue':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
version: 1.0.3(vite@8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))
|
||||
vue-tsc:
|
||||
specifier: ^3.1.8
|
||||
version: 3.2.1(typescript@5.9.3)
|
||||
@@ -12137,8 +12137,8 @@ packages:
|
||||
resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
injeca@0.1.5:
|
||||
resolution: {integrity: sha512-fufRnru65A5mHQ8L7mZpmJ5FHOjoqJl9Cfwbv+rcclcYWzScxcWFNbAyrH5Fkor0qccHFxBp3+yRc8sNC1yZSQ==}
|
||||
injeca@0.1.7:
|
||||
resolution: {integrity: sha512-Nh166QCTJbshSNBTWGzdGNDkxMKvHALJfCW70zYJ9AF+4FDQLXRcjGJnaaazW7RZWLQsnWEvK5/sXMnexUjvBg==}
|
||||
peerDependencies:
|
||||
'@guiiai/logg': '>= 1'
|
||||
error-stack-parser: ^2.1.4
|
||||
@@ -22467,6 +22467,12 @@ snapshots:
|
||||
vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@vitejs/plugin-vue@6.0.3(vite@8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.53
|
||||
vite: 8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@vitest/browser-playwright@4.0.16(bufferutil@4.1.0)(playwright@1.57.0)(utf-8-validate@5.0.10)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.16)':
|
||||
dependencies:
|
||||
'@vitest/browser': 4.0.16(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.0.16)
|
||||
@@ -23226,9 +23232,9 @@ snapshots:
|
||||
'@types/filesystem': 0.0.36
|
||||
'@types/har-format': 1.2.16
|
||||
|
||||
'@wxt-dev/module-vue@1.0.3(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
'@wxt-dev/module-vue@1.0.3(vite@8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))(wxt@0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@vitejs/plugin-vue': 6.0.3(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))
|
||||
'@vitejs/plugin-vue': 6.0.3(vite@8.0.0-beta.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.5.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.25(typescript@5.9.3))
|
||||
wxt: 0.20.13(@types/node@24.10.4)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.30.2)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- vite
|
||||
@@ -26657,7 +26663,7 @@ snapshots:
|
||||
|
||||
ini@4.1.3: {}
|
||||
|
||||
injeca@0.1.5(@guiiai/logg@1.2.11)(error-stack-parser@2.1.4)(nanoid@5.1.6):
|
||||
injeca@0.1.7(@guiiai/logg@1.2.11)(error-stack-parser@2.1.4)(nanoid@5.1.6):
|
||||
dependencies:
|
||||
error-stack-parser: 2.1.4
|
||||
nanoid: 5.1.6
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ catalog:
|
||||
es-toolkit: 1.43.0
|
||||
hono: 4.11.3
|
||||
idb-keyval: ^6.2.2
|
||||
injeca: ^0.1.5
|
||||
injeca: ^0.1.7
|
||||
is-network-error: ^1.3.0
|
||||
nano-staged: ^0.9.0
|
||||
nanoid: 5.1.6
|
||||
|
||||
Reference in New Issue
Block a user