refactor(extension): 重构 AI Chat功能

- 移除侧边栏视图,改为使用状态栏按钮打开 Webview 面板
- 添加文件选择和文件列表获取功能
- 优化 Webview 面板的创建和状态管理
- 更新 API 调用和消息处理逻辑
-调整包配置,增加 ESLint 和 TypeScript 依赖
master
钟良源 6 months ago
parent 804aac5807
commit e05c4de38f

1787
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -4,27 +4,26 @@
"description": "Open an AI chat webview panel from the status bar",
"version": "0.0.1",
"publisher": "your-publisher",
"main": "./out/extension.js",
"engines": {
"vscode": "^1.60.0"
},
"main": "./out/extension.js",
"activationEvents": [
"*",
"onCommand:ai-chat.openWebview",
"onView:ai-chat-sidebar-view"
],
"contributes": {
"viewsContainers": {
"secondarySidebar": [
"activitybar": [
{
"id": "ai-chat-sidebar",
"id": "ai-chat",
"title": "AI Chat",
"icon": "media/icon.png"
}
]
},
"views": {
"ai-chat-sidebar": [
"ai-chat": [
{
"id": "ai-chat-sidebar-view",
"name": "AI Chat",
@ -53,15 +52,19 @@
]
},
"scripts": {
"compile": "tsc",
"watch": "tsc -w",
"debug": "node --inspect-brk -r ts-node/register src/extension.ts"
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -w -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src --ext ts"
},
"devDependencies": {
"@types/node": "^16.11.7",
"@types/vscode": "^1.60.0",
"typescript": "^4.5.2",
"vscode": "^1.1.37"
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
"typescript": "^4.9.4"
},
"dependencies": {
"diff": "^8.0.2",

@ -1,433 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
diff:
specifier: ^8.0.2
version: 8.0.2
highlight.js:
specifier: ^11.11.1
version: 11.11.1
marked:
specifier: ^16.1.1
version: 16.1.1
devDependencies:
'@types/node':
specifier: ^16.11.7
version: 16.18.126
'@types/vscode':
specifier: ^1.60.0
version: 1.102.0
typescript:
specifier: ^4.5.2
version: 4.9.5
vscode:
specifier: ^1.1.37
version: 1.1.37
packages:
'@tootallnate/once@1.1.2':
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
engines: {node: '>= 6'}
'@types/node@16.18.126':
resolution: {integrity: sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==}
'@types/vscode@1.102.0':
resolution: {integrity: sha512-V9sFXmcXz03FtYTSUsYsu5K0Q9wH9w9V25slddcxrh5JgORD14LpnOA7ov0L9ALi+6HrTjskLJ/tY5zeRF3TFA==}
agent-base@4.3.0:
resolution: {integrity: sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==}
engines: {node: '>= 4.0.0'}
agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
browser-stdout@1.3.1:
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
commander@2.15.1:
resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
debug@3.1.0:
resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
diff@3.5.0:
resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==}
engines: {node: '>=0.3.1'}
diff@8.0.2:
resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
engines: {node: '>=0.3.1'}
es6-promise@4.2.8:
resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==}
es6-promisify@5.0.0:
resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==}
escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
glob@7.1.2:
resolution: {integrity: sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==}
deprecated: Glob versions prior to v9 are no longer supported
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
growl@1.10.5:
resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==}
engines: {node: '>=4.x'}
has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
he@1.1.1:
resolution: {integrity: sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==}
hasBin: true
highlight.js@11.11.1:
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
engines: {node: '>=12.0.0'}
http-proxy-agent@2.1.0:
resolution: {integrity: sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==}
engines: {node: '>= 4.5.0'}
http-proxy-agent@4.0.1:
resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
engines: {node: '>= 6'}
https-proxy-agent@2.2.4:
resolution: {integrity: sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==}
engines: {node: '>= 4.5.0'}
https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
marked@16.1.1:
resolution: {integrity: sha512-ij/2lXfCRT71L6u0M29tJPhP0bM5shLL3u5BePhFwPELj2blMJ6GDtD7PfJhRLhJ/c2UwrK17ySVcDzy2YHjHQ==}
engines: {node: '>= 20'}
hasBin: true
minimatch@3.0.4:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimist@0.0.8:
resolution: {integrity: sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==}
mkdirp@0.5.1:
resolution: {integrity: sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==}
deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
hasBin: true
mocha@5.2.0:
resolution: {integrity: sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==}
engines: {node: '>= 4.0.0'}
hasBin: true
ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
hasBin: true
source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
supports-color@5.4.0:
resolution: {integrity: sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==}
engines: {node: '>=4'}
typescript@4.9.5:
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
engines: {node: '>=4.2.0'}
hasBin: true
vscode-test@0.4.3:
resolution: {integrity: sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==}
engines: {node: '>=8.9.3'}
deprecated: This package has been renamed to @vscode/test-electron, please update to the new name
vscode@1.1.37:
resolution: {integrity: sha512-vJNj6IlN7IJPdMavlQa1KoFB3Ihn06q1AiN3ZFI/HfzPNzbKZWPPuiU+XkpNOfGU5k15m4r80nxNPlM7wcc0wg==}
engines: {node: '>=8.9.3'}
deprecated: 'This package is deprecated in favor of @types/vscode and vscode-test. For more information please read: https://code.visualstudio.com/updates/v1_36#_splitting-vscode-package-into-typesvscode-and-vscodetest'
hasBin: true
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
snapshots:
'@tootallnate/once@1.1.2': {}
'@types/node@16.18.126': {}
'@types/vscode@1.102.0': {}
agent-base@4.3.0:
dependencies:
es6-promisify: 5.0.0
agent-base@6.0.2:
dependencies:
debug: 4.4.1
transitivePeerDependencies:
- supports-color
balanced-match@1.0.2: {}
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
browser-stdout@1.3.1: {}
buffer-from@1.1.2: {}
commander@2.15.1: {}
concat-map@0.0.1: {}
debug@3.1.0(supports-color@5.4.0):
dependencies:
ms: 2.0.0
optionalDependencies:
supports-color: 5.4.0
debug@3.2.7:
dependencies:
ms: 2.1.3
debug@4.4.1:
dependencies:
ms: 2.1.3
diff@3.5.0: {}
diff@8.0.2: {}
es6-promise@4.2.8: {}
es6-promisify@5.0.0:
dependencies:
es6-promise: 4.2.8
escape-string-regexp@1.0.5: {}
fs.realpath@1.0.0: {}
glob@7.1.2:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
growl@1.10.5: {}
has-flag@3.0.0: {}
he@1.1.1: {}
highlight.js@11.11.1: {}
http-proxy-agent@2.1.0:
dependencies:
agent-base: 4.3.0
debug: 3.1.0(supports-color@5.4.0)
transitivePeerDependencies:
- supports-color
http-proxy-agent@4.0.1:
dependencies:
'@tootallnate/once': 1.1.2
agent-base: 6.0.2
debug: 4.4.1
transitivePeerDependencies:
- supports-color
https-proxy-agent@2.2.4:
dependencies:
agent-base: 4.3.0
debug: 3.2.7
transitivePeerDependencies:
- supports-color
https-proxy-agent@5.0.1:
dependencies:
agent-base: 6.0.2
debug: 4.4.1
transitivePeerDependencies:
- supports-color
inflight@1.0.6:
dependencies:
once: 1.4.0
wrappy: 1.0.2
inherits@2.0.4: {}
marked@16.1.1: {}
minimatch@3.0.4:
dependencies:
brace-expansion: 1.1.12
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
minimist@0.0.8: {}
mkdirp@0.5.1:
dependencies:
minimist: 0.0.8
mocha@5.2.0:
dependencies:
browser-stdout: 1.3.1
commander: 2.15.1
debug: 3.1.0(supports-color@5.4.0)
diff: 3.5.0
escape-string-regexp: 1.0.5
glob: 7.1.2
growl: 1.10.5
he: 1.1.1
minimatch: 3.0.4
mkdirp: 0.5.1
supports-color: 5.4.0
ms@2.0.0: {}
ms@2.1.3: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
path-is-absolute@1.0.1: {}
semver@5.7.2: {}
source-map-support@0.5.21:
dependencies:
buffer-from: 1.1.2
source-map: 0.6.1
source-map@0.6.1: {}
supports-color@5.4.0:
dependencies:
has-flag: 3.0.0
typescript@4.9.5: {}
vscode-test@0.4.3:
dependencies:
http-proxy-agent: 2.1.0
https-proxy-agent: 2.2.4
transitivePeerDependencies:
- supports-color
vscode@1.1.37:
dependencies:
glob: 7.2.3
http-proxy-agent: 4.0.1
https-proxy-agent: 5.0.1
mocha: 5.2.0
semver: 5.7.2
source-map-support: 0.5.21
vscode-test: 0.4.3
transitivePeerDependencies:
- supports-color
wrappy@1.0.2: {}

@ -6,31 +6,27 @@ import * as diff from 'diff';
let panel: vscode.WebviewPanel | undefined;
let currentSessionHistory: { role: string; content: string }[] = [];
// 存储工作区变更的文件,包含状态信息
let workspaceChanges: {
[key: string]: {
original: string;
modified: string;
status: 'pending' | 'accepted' | 'rejected'
}
let workspaceChanges: {
[key: string]: {
original: string;
modified: string;
status: 'pending' | 'accepted' | 'rejected'
}
} = {};
export function activate(context: vscode.ExtensionContext) {
console.log('Extension activated');
// 注册辅助侧边栏视图提供商
const provider = new AISidebarViewProvider(context.extensionPath, context);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
'ai-chat-sidebar-view',
provider,
{ webviewOptions: { retainContextWhenHidden: true } }
)
);
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
statusBarItem.text = "$(comment) Chat";
statusBarItem.tooltip = "Open AI Chat";
statusBarItem.command = "ai-chat.openWebview";
statusBarItem.show();
// 注册打开 Webview 的命令(保持向后兼容)
// 注册打开 Webview 的命令
const openWebviewCommand = vscode.commands.registerCommand("ai-chat.openWebview", () => {
// 聚焦到辅助侧边栏视图
vscode.commands.executeCommand('workbench.action.focusAuxiliaryBar');
openWebview(context.extensionPath, context.globalState, context);
});
// 注册右键菜单命令:添加选中内容到对话
@ -50,43 +46,147 @@ export function activate(context: vscode.ExtensionContext) {
const selectedText = editor.document.getText(selection);
// 发送选中的代码到侧边栏视图
provider.sendSelectedCode(selectedText);
// 检查 panel 是否存在
if (panel && panel.webview) {
panel.webview.postMessage({
command: 'addToInput',
role: 'user',
content: selectedText
});
} else {
vscode.window.showWarningMessage("请先打开对话窗口");
}
});
// 注册选择文件命令
const selectFileCommand = vscode.commands.registerCommand('ai-chat.selectFileAsContext', async () => {
const uris = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false, // 单选
title: "选择一个文件作为上下文"
});
if (uris && uris.length > 0) {
const selectedFileUri = uris[0];
const fileContent = await vscode.workspace.fs.readFile(selectedFileUri);
const decodedContent = new TextDecoder("utf-8").decode(fileContent);
// 发送文件内容到 Webview
if (panel && panel.webview) {
panel.webview.postMessage({
command: 'setContextFile',
fileName: selectedFileUri.fsPath,
fileContent: decodedContent
});
}
}
});
// 注册获取工作区文件列表的命令
const getFileListCommand = vscode.commands.registerCommand('ai-chat.getFileList', async () => {
if (panel && panel.webview) {
try {
// 获取工作区根路径
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
panel.webview.postMessage({
command: 'fileList',
files: [],
error: '未找到工作区'
});
return;
}
const rootPath = workspaceFolders[0].uri;
// 查找所有文件
const fileUris = await vscode.workspace.findFiles('**/*', '**/node_modules/**');
// 转换为相对路径和基本信息
const files = fileUris.map(uri => {
const relativePath = vscode.workspace.asRelativePath(uri);
const isDirectory = uri.path.endsWith('/');
return {
path: uri.fsPath,
relativePath: relativePath,
name: path.basename(uri.fsPath),
isDirectory: isDirectory
};
});
panel?.webview.postMessage({
command: 'fileList',
files: files
});
} catch (error: any) {
panel?.webview.postMessage({
command: 'fileList',
files: [],
error: error.message
});
}
}
});
// 添加到 subscriptions
context.subscriptions.push(openWebviewCommand, addToChatCommand);
context.subscriptions.push(statusBarItem, openWebviewCommand, addToChatCommand,selectFileCommand,getFileListCommand);
}
class AISidebarViewProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'ai-chat-sidebar-view';
private _view?: vscode.WebviewView;
private _extensionPath: string;
private _context: vscode.ExtensionContext;
constructor(extensionPath: string, context: vscode.ExtensionContext) {
this._extensionPath = extensionPath;
this._context = context;
function openWebview(
extensionPath: string,
globalState: vscode.Memento,
context: vscode.ExtensionContext // 添加 context 参数
) {
// 如果面板已经存在,直接显示它而不是创建新的
if (panel) {
panel.reveal(vscode.ViewColumn.Beside);
// 发送当前状态到 WebView
if (panel.webview) {
panel.webview.postMessage({
command: 'restoreState',
history: currentSessionHistory,
workspaceChanges: workspaceChanges
});
}
return;
}
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
this._view = webviewView;
webviewView.webview.options = {
panel = vscode.window.createWebviewPanel(
'aiChatWebview',
'AI Chat',
vscode.ViewColumn.Beside,
{
enableScripts: true,
localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, 'media', 'webview'))
]
};
localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'media', 'webview'))]
}
);
const styleUri = panel.webview.asWebviewUri(
vscode.Uri.file(path.join(extensionPath, 'media', 'webview', 'style.css'))
);
const scriptUri = panel.webview.asWebviewUri(
vscode.Uri.file(path.join(extensionPath, 'media', 'webview', 'script.js'))
);
const highlightScriptUri = panel.webview.asWebviewUri(
vscode.Uri.file(path.join(extensionPath, 'media', 'webview', 'highlight.min.js'))
);
const highlightStyleUri = panel.webview.asWebviewUri(
vscode.Uri.file(path.join(extensionPath, 'media', 'webview', 'styles', 'atom-one-dark.css'))
);
const markedScriptUri = panel.webview.asWebviewUri(
vscode.Uri.file(path.join(context.extensionPath, 'media', 'webview', 'marked.min.js'))
);
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
panel.webview.html = getWebviewContent(styleUri, scriptUri,highlightScriptUri,highlightStyleUri,markedScriptUri);
webviewView.webview.onDidReceiveMessage(async (message) => {
panel.webview.onDidReceiveMessage(
async (message) => {
switch (message.command) {
case 'ask':
const question = message.text;
@ -95,10 +195,10 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
currentSessionHistory = [...history, { role: 'user', content: question }];
this._postMessage({ command: 'addMessage', role: 'user', content: question });
panel?.webview.postMessage({ command: 'addMessage', role: 'user', content: question });
try {
const response = await callQwenAPI(question, history, fileContent, this._context);
const response = await callQwenAPI(question, history, fileContent, context);
console.log("response:", response);
const aiMessage = response.choices[0]?.message?.content || '未获取到回复';
@ -115,7 +215,7 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
};
// 通知 WebView 更新工作区文件列表
this._postMessage({
panel?.webview.postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
@ -124,59 +224,68 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
// 更新当前会话历史
currentSessionHistory = [...currentSessionHistory, { role: 'assistant', content: aiMessage }];
this._postMessage({
panel?.webview.postMessage({
command: 'addMessage',
role: 'ai',
content: aiMessage,
codeDiff: codeDiff // 发送代码差异信息
});
} catch (error) {
this._postMessage({ command: 'addMessage', role: 'ai', content: '调用失败:' + error.message });
} catch (error: any) {
panel?.webview.postMessage({ command: 'addMessage', role: 'ai', content: '调用失败:' + error.message });
}
this._postMessage({ command: 'hideLoading' });
panel?.webview.postMessage({ command: 'hideLoading' });
break;
case 'selectContextFile':
// 执行文件选择逻辑
const uris = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
title: "选择一个文件作为上下文"
});
try {
const uris = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
title: "选择一个文件作为上下文"
});
if (uris && uris.length > 0) {
const selectedFileUri = uris[0];
const fileContent = await vscode.workspace.fs.readFile(selectedFileUri);
const decodedContent = new TextDecoder("utf-8").decode(fileContent);
if (uris && uris.length > 0) {
const selectedFileUri = uris[0];
const fileContent = await vscode.workspace.fs.readFile(selectedFileUri);
const decodedContent = new TextDecoder("utf-8").decode(fileContent);
this._postMessage({
command: 'setContextFile',
fileName: selectedFileUri.fsPath,
fileContent: decodedContent
});
panel?.webview.postMessage({
command: 'setContextFile',
fileName: selectedFileUri.fsPath,
fileContent: decodedContent
});
}
} catch (error: any) {
// 忽略错误
console.error(`文件操作错误: ${error.message}`);
}
break;
case 'getFileList':
// 执行获取文件列表逻辑
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) {
const fileUris = await vscode.workspace.findFiles('**/*', '**/node_modules/**');
const files = fileUris.map(uri => {
const relativePath = vscode.workspace.asRelativePath(uri);
return {
path: uri.fsPath,
relativePath: relativePath,
name: path.basename(uri.fsPath),
isDirectory: false // 简化处理,实际可以根据扩展名判断
};
});
try {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) {
const fileUris = await vscode.workspace.findFiles('**/*', '**/node_modules/**');
const files = fileUris.map(uri => {
const relativePath = vscode.workspace.asRelativePath(uri);
return {
path: uri.fsPath,
relativePath: relativePath,
name: path.basename(uri.fsPath),
isDirectory: false // 简化处理,实际可以根据扩展名判断
};
});
this._postMessage({
command: 'fileList',
files: files
});
panel?.webview.postMessage({
command: 'fileList',
files: files
});
}
} catch (error: any) {
// 忽略错误
}
break;
@ -187,12 +296,12 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
const fileContent = await vscode.workspace.fs.readFile(fileUri);
const decodedContent = new TextDecoder("utf-8").decode(fileContent);
this._postMessage({
panel?.webview.postMessage({
command: 'setContextFile',
fileName: fileUri.fsPath,
fileContent: decodedContent
});
} catch (error) {
} catch (error: any) {
vscode.window.showErrorMessage(`无法读取文件: ${error.message}`);
}
break;
@ -225,7 +334,7 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
modifiedUri,
'Code Diff - Original ↔ Modified'
);
} catch (error) {
} catch (error: any) {
vscode.window.showErrorMessage(`显示代码差异失败: ${error.message}`);
}
break;
@ -245,13 +354,13 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
}
// 通知 WebView 更新工作区文件列表
this._postMessage({
panel?.webview.postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
vscode.window.showInformationMessage('代码变更已应用');
} catch (error) {
} catch (error: any) {
vscode.window.showErrorMessage(`应用代码变更失败: ${error.message}`);
}
break;
@ -266,13 +375,13 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
}
// 通知 WebView 更新工作区文件列表
this._postMessage({
panel?.webview.postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
vscode.window.showInformationMessage('代码变更已拒绝');
} catch (error) {
} catch (error: any) {
vscode.window.showErrorMessage(`拒绝代码变更失败: ${error.message}`);
}
break;
@ -303,13 +412,13 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
viewColumn: vscode.ViewColumn.One // 在第一列打开文件
});
}
} catch (error) {
} catch (error: any) {
vscode.window.showErrorMessage(`打开文件失败: ${error.message}`);
}
break;
case 'getCodeChanges':
// 获取代码变更
this._postMessage({
panel?.webview.postMessage({
command: 'codeChanges',
changes: workspaceChanges
});
@ -320,7 +429,7 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
// 恢复显示历史消息
currentSessionHistory.forEach(msg => {
this._postMessage({
panel?.webview.postMessage({
command: 'addMessage',
role: msg.role,
content: msg.content,
@ -330,56 +439,23 @@ class AISidebarViewProvider implements vscode.WebviewViewProvider {
// 恢复工作区变更
if (message.workspaceChanges) {
this._postMessage({
panel?.webview.postMessage({
command: 'updateWorkspaceFiles',
files: message.workspaceChanges
});
}
break;
}
});
}
public sendSelectedCode(code: string) {
if (this._view) {
this._view.show?.(true);
this._postMessage({
command: 'addToInput',
role: 'user',
content: code
});
}
}
private _getHtmlForWebview(webview: vscode.Webview) {
const styleUri = webview.asWebviewUri(
vscode.Uri.file(path.join(this._extensionPath, 'media', 'webview', 'style.css'))
);
const scriptUri = webview.asWebviewUri(
vscode.Uri.file(path.join(this._extensionPath, 'media', 'webview', 'script.js'))
);
const highlightScriptUri = webview.asWebviewUri(
vscode.Uri.file(path.join(this._extensionPath, 'media', 'webview', 'highlight.min.js'))
);
const highlightStyleUri = webview.asWebviewUri(
vscode.Uri.file(path.join(this._extensionPath, 'media', 'webview', 'styles', 'atom-one-dark.css'))
);
const markedScriptUri = webview.asWebviewUri(
vscode.Uri.file(path.join(this._extensionPath, 'media', 'webview', 'marked.min.js'))
);
return getWebviewContent(styleUri, scriptUri, highlightScriptUri, highlightStyleUri, markedScriptUri);
}
},
undefined,
context.subscriptions
);
private _postMessage(message: any) {
if (this._view) {
this._view.webview.postMessage(message);
}
}
// // 添加 dispose 监听器
panel.onDidDispose(() => {
// 不再清空会话历史,让用户在重新打开面板时保留上下文
panel = undefined;
}, null, context.subscriptions);
}
async function callQwenAPI(
@ -427,9 +503,9 @@ async function callQwenAPI(
}
return await response.json();
} catch (e) {
console.error(e);
throw e;
} catch (error: any) {
console.error(error);
throw error;
}
}
@ -572,4 +648,4 @@ function getWebviewContent(styleUri: vscode.Uri, scriptUri: vscode.Uri,highlight
<script src="${markedScriptUri}"></script>
</body>
</html>`;
}
}

@ -5,7 +5,7 @@
"outDir": "./out",
"rootDir": "./src",
"moduleResolution": "node",
"declaration": true,
"declaration": false,
"sourceMap": true,
"esModuleInterop": true,
"strict": true,

Loading…
Cancel
Save