Compare commits

...

7 Commits

Author SHA1 Message Date
钟良源 df345447ca fix(MessageHandler): 优化网络连接中断的错误处理
- 在 readMessage 中增加对网络连接中断错误的处理逻辑
- 在 modelApi 中增加对不同错误类型的捕获和处理
- 优化错误信息提示,提高用户体验
6 months ago
钟良源 6074d35ef8 build: 更新项目版本并添加打包命令
- 将 package.json 中的版本号从 0.0.2 修改为 0.0.5
- 在 package.json 的 scripts 中添加 package 命令,用于打包扩展
- 更新 package-lock.json 中的版本号和依赖信息
6 months ago
钟良源 76b55ec905 chroe: 版本更新 0.0.1 -> 0.0.2 6 months ago
钟良源 9e859d8a20 refactor(webview): 重构网页视图功能
- 移除了 index.html 文件
- 优化了 MessageHandler.ts,增加了日志记录功能
- 更新了 script.js,改进了用户输入和请求取消的处理逻辑
- 调整了 style.css,移除了不必要的样式
- 更新了 webView.ts,同步了聊天输入表单的结构
6 months ago
钟良源 84964a630d refactor(webview): 重构网页视图功能
- 移除未使用的导入和函数
-优化消息处理和流式响应逻辑
- 添加会话历史记录功能
- 改进文件浏览器和模态框交互
- 重新添加语言检测和文件扩展名功能
- 新增停止按钮样式
6 months ago
钟良源 24e437f647 feat(chat): 实现流式传输功能并优化聊天界面
- 新增流式传输相关功能,包括开始流、处理数据和结束流
- 优化聊天界面样式,增加消息发送和停止按钮
- 改进代码差异显示和文件创建功能
- 添加用户配置支持,用于自定义API地址和密钥
- 重构部分代码以提高可维护性
6 months ago
钟良源 cedac4ff73 feat:更新git忽略文件 6 months ago

1
.gitignore vendored

@ -4,3 +4,4 @@ node_modules
.vscode-test/
*.vsix
.idea
ai-chat-*

@ -1,5 +1,10 @@
let selectedCodeForContext = '';
let contextFilePath = ''; // 存储当前上下文文件路径
let streamingMessageElement = null; // 用于存储当前流式消息的DOM元素
let streamingMessageContent = ''; // 用于存储当前流式消息的内容
let currentReader = null; // 用于存储当前流的reader以便可以取消
let isRequestInProgress = false; // 标记是否有请求正在进行
let currentSessionHistory = []; // 存储当前会话历史
const vscode = acquireVsCodeApi();
document.addEventListener('DOMContentLoaded', () => {
@ -7,18 +12,36 @@ document.addEventListener('DOMContentLoaded', () => {
const userInput = document.getElementById('user-input');
const chatBox = document.getElementById('chat-box');
const loading = document.getElementById('loading');
const sendBtn = document.getElementById('send-btn');
// 初始化隐藏工作区
// 初始化显示工作区容器
const workspaceContainer = document.querySelector('.workspace-container');
if (workspaceContainer) {
workspaceContainer.style.display = 'none';
}
chatForm.addEventListener('submit', (e) => {
chatForm.addEventListener('submit', async (e) => {
e.preventDefault();
const text = userInput.value.trim();
if (!text) return;
// 如果输入为空,不执行任何操作
if (!text && !isRequestInProgress) return;
// 如果有请求正在进行,点击按钮将中断请求
if (isRequestInProgress) {
logToExtension("取消请求");
// 发送取消请求的消息到扩展
vscode.postMessage({
command: 'cancelRequest'
});
// 重置按钮状态
resetSendButton();
hideLoading();
return;
}
// 发送消息
vscode.postMessage({
command: 'ask',
text,
@ -26,11 +49,37 @@ document.addEventListener('DOMContentLoaded', () => {
fileContentPath: contextFilePath // 添加文件路径
});
// addMessage('user', text);
// 切换按钮为停止按钮
isRequestInProgress = true;
if (sendBtn) {
sendBtn.textContent = '停止';
sendBtn.classList.add('stop-button');
}
showLoading();
userInput.value = '';
});
// 重置发送按钮为初始状态
function resetSendButton() {
isRequestInProgress = false;
const sendBtn = document.getElementById('send-btn');
if (sendBtn) {
sendBtn.textContent = '发送';
sendBtn.classList.remove('stop-button');
}
}
// 显示加载状态
function showLoading() {
loading.classList.remove('hidden');
chatBox.scrollTop = chatBox.scrollHeight;
}
// 隐藏加载状态
function hideLoading() {
loading.classList.add('hidden');
}
// 判断内容是否为 Markdown简单判断
function isMarkdown(content) {
if (!content || content.trim() === '') return false;
@ -49,39 +98,45 @@ document.addEventListener('DOMContentLoaded', () => {
}
// 处理添加消息
function addMessage(role, content, codeDiff) {
function addMessage(role, content, codeDiffs) {
const msgDiv = document.createElement('div');
msgDiv.className = `message ${role}`;
// 渲染内容
if (isMarkdown(content)) {
console.log("content:", content);
msgDiv.innerHTML = marked.parse(content);
console.log("msgDiv.innerHTML:", msgDiv.innerHTML);
} else {
const div = document.createElement('div');
div.textContent = content;
msgDiv.appendChild(div);
}
// 如果有代码差异,添加查看差异按钮
if (codeDiff) {
const diffButton = document.createElement('button');
diffButton.className = 'diff-button';
diffButton.textContent = '查看代码差异';
diffButton.onclick = () => showCodeDiff(codeDiff);
msgDiv.appendChild(diffButton);
}
// 如果是AI消息且包含代码块添加"创建文件"按钮
// 如果是AI消息且包含代码块为每个代码块添加按钮
if (role === 'ai') {
const codeBlocks = msgDiv.querySelectorAll('pre code');
// 确保 codeDiffs 是数组
const diffs = Array.isArray(codeDiffs) ? codeDiffs : [];
codeBlocks.forEach((block, index) => {
// 为每个代码块添加"创建文件"按钮
const createFileButton = document.createElement('button');
createFileButton.className = 'create-file-button';
createFileButton.textContent = `生成代码文件`;
createFileButton.onclick = () => createNewFileFromCode(block.textContent, index);
msgDiv.appendChild(createFileButton);
// 将按钮插入到代码块后面
block.parentNode.after(createFileButton);
// 如果有代码差异,也为每个代码块添加查看差异按钮
if (diffs[index]) {
const diffButton = document.createElement('button');
diffButton.className = 'diff-button';
diffButton.textContent = `查看代码差异`;
diffButton.onclick = () => showCodeDiff(diffs[index]);
// 将差异按钮插入到创建文件按钮后面
createFileButton.after(diffButton);
}
});
}
@ -97,6 +152,150 @@ document.addEventListener('DOMContentLoaded', () => {
// 滚动到底部
chatBox.scrollTop = chatBox.scrollHeight;
return msgDiv; // 返回创建的消息元素
}
// 开始流式传输
function startStreaming() {
// 创建一个新的AI消息元素
streamingMessageElement = document.createElement('div');
streamingMessageElement.className = 'message ai streaming';
// 添加流式消息内容容器
const contentDiv = document.createElement('div');
contentDiv.className = 'streaming-content';
streamingMessageElement.appendChild(contentDiv);
// 添加光标元素
const cursor = document.createElement('span');
cursor.className = 'streaming-cursor';
cursor.textContent = '▋';
streamingMessageElement.appendChild(cursor);
chatBox.appendChild(streamingMessageElement);
streamingMessageContent = '';
// 滚动到底部
chatBox.scrollTop = chatBox.scrollHeight;
}
// 处理流式数据
function handleStreamData(data) {
if (!streamingMessageElement) return;
let processedData = data;
if (data.startsWith('data:')) {
// 移除data:前缀
processedData = data.replace(/^data:/, '');
// 逐个匹配换行符,当出现两个或更多连续换行符时删除多余的
processedData = processedData.replace(/^(\n{2,})/, (match) => {
// 删除所有连续的换行符
return '';
});
}
// 更新内容
streamingMessageContent += processedData;
// 更新显示
const contentDiv = streamingMessageElement.querySelector('.streaming-content');
if (contentDiv) {
// 如果是Markdown内容我们需要特殊处理
if (isMarkdown(streamingMessageContent)) {
contentDiv.innerHTML = marked.parse(streamingMessageContent);
} else {
contentDiv.textContent = streamingMessageContent;
}
// 高亮代码块
const codeBlocks = contentDiv.querySelectorAll('pre code');
codeBlocks.forEach(block => {
if (hljs && !block.dataset.highlighted) {
hljs.highlightElement(block);
block.dataset.highlighted = 'true';
}
});
}
// 滚动到底部
chatBox.scrollTop = chatBox.scrollHeight;
}
// 结束流式传输
function endStreaming(content, codeDiffs = null) {
if (streamingMessageElement) {
// 移除流式传输类和光标
streamingMessageElement.classList.remove('streaming');
const cursor = streamingMessageElement.querySelector('.streaming-cursor');
if (cursor) {
cursor.remove();
}
// 如果是AI消息且包含代码块为每个代码块添加按钮
if (streamingMessageElement.classList.contains('ai')) {
const codeBlocks = streamingMessageElement.querySelectorAll('pre code');
codeBlocks.forEach((block, index) => {
// 为每个代码块添加"创建文件"按钮
const createFileButton = document.createElement('button');
createFileButton.className = 'create-file-button';
createFileButton.textContent = `生成代码文件`;
createFileButton.onclick = () => createNewFileFromCode(block.textContent, index);
// 将按钮插入到代码块后面
block.parentNode.after(createFileButton);
// 如果有代码差异,也为每个代码块添加查看差异按钮
// codeDiffs是一个数组每个元素对应一个代码块的差异信息
if (codeDiffs && codeDiffs[index]) {
const diffButton = document.createElement('button');
diffButton.className = 'diff-button';
diffButton.textContent = `查看代码差异`;
diffButton.onclick = () => showCodeDiff(codeDiffs[index]);
// 将差异按钮插入到创建文件按钮后面
createFileButton.after(diffButton);
}
});
}
// 高亮代码块
const codeBlocks = streamingMessageElement.querySelectorAll('pre code');
codeBlocks.forEach(block => {
if (hljs) {
hljs.highlightElement(block);
}
});
streamingMessageElement = null;
streamingMessageContent = '';
}
// 重置发送按钮状态
resetSendButton();
// 保存消息到历史记录
currentSessionHistory.push({role: 'assistant', content: content});
hideLoading();
}
// 处理请求取消
function handleRequestCancelled() {
// 重置发送按钮
resetSendButton();
// 如果有正在流式传输的消息,结束它
if (streamingMessageElement) {
streamingMessageElement.classList.remove('streaming');
const cursor = streamingMessageElement.querySelector('.streaming-cursor');
if (cursor) {
cursor.remove();
}
streamingMessageElement = null;
streamingMessageContent = '';
}
hideLoading();
}
// 显示代码差异
@ -184,14 +383,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
function showLoading() {
loading.classList.remove('hidden');
}
function hideLoading() {
loading.classList.add('hidden');
}
// 更新工作区文件列表
function updateWorkspaceFiles(files) {
const workspaceContainer = document.querySelector('.workspace-container');
@ -253,9 +444,20 @@ document.addEventListener('DOMContentLoaded', () => {
window.addEventListener('message', (event) => {
const message = event.data;
console.log(message)
if (message.command === 'addMessage') {
addMessage(message.role, message.content, message.codeDiff);
} else if (message.command === 'startStream') {
// 开始流式传输
startStreaming();
} else if (message.command === 'streamData') {
// 处理流式数据
handleStreamData(message.data);
} else if (message.command === 'endStream') {
// 结束流式传输
endStreaming(message.content, message.codeDiffs);
} else if (message.command === 'requestCancelled') {
// 请求被取消
handleRequestCancelled();
} else if (message.command === 'hideLoading') {
hideLoading();
} else if (message.command === 'addToInput') {
@ -339,15 +541,21 @@ document.addEventListener('DOMContentLoaded', () => {
});
// 打开文件浏览器
document.getElementById('select-context-btn').addEventListener('click', () => {
const selectContextBtn = document.getElementById('select-context-btn');
if (selectContextBtn) {
selectContextBtn.addEventListener('click', () => {
// 显示模态框
document.getElementById('file-browser-modal').classList.remove('hidden');
const fileBrowserModal = document.getElementById('file-browser-modal');
if (fileBrowserModal) {
fileBrowserModal.classList.remove('hidden');
}
// 请求文件列表
vscode.postMessage({
command: 'getFileList'
});
});
}
// 关闭模态框
document.getElementById('close-modal').addEventListener('click', () => {
document.getElementById('file-browser-modal').classList.add('hidden');
@ -392,6 +600,7 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('diff-modal').classList.add('hidden');
}
});
// 从代码创建新文件
function createNewFileFromCode(codeContent, index) {
// 简单的语言检测
@ -407,6 +616,14 @@ document.addEventListener('DOMContentLoaded', () => {
language: language
});
}
function logToExtension(...args) {
vscode.postMessage({
command: 'log',
data: args
});
}
// 简单的语言检测
function detectLanguage(code) {

@ -88,20 +88,28 @@ code {
#chat-form button {
padding: 8px 16px;
background-color: var(--vscode-button-background);
color: var(--vscode-button-foreground);
background-color: #007acc;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
font-size: 14px;
transition: background-color 0.2s;
}
#chat-form button:hover {
background-color: var(--vscode-button-hoverBackground);
background-color: #005a9e;
}
/* 停止按钮样式 */
#chat-form button.stop-button {
background-color: #f44336;
}
#chat-form button.stop-button:hover {
background-color: #d32f2f;
}
/* 加载提示 */
#loading {
text-align: center;
padding: 10px;
@ -388,6 +396,40 @@ code {
background-color: #45a049;
}
.code-button-container {
display: flex;
gap: 10px;
margin-top: 10px;
flex-wrap: wrap;
}
.code-button-container .create-file-button {
margin: 0;
}
/* 流式消息样式 */
.message.ai.streaming {
position: relative;
}
.streaming-content {
display: inline-block;
width: 100%;
}
.streaming-cursor {
display: inline-block;
width: 0.7em;
animation: blink 1s infinite;
color: var(--vscode-editor-foreground);
vertical-align: baseline;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* 工作区容器 */
.workspace-container {
margin: 10px 0;

7
package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "ai-chat",
"version": "0.0.1",
"version": "0.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ai-chat",
"version": "0.0.1",
"version": "0.0.3",
"dependencies": {
"diff": "^8.0.2",
"highlight.js": "^11.11.1",
@ -19,6 +19,9 @@
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
"typescript": "^4.9.4"
},
"engines": {
"vscode": "^1.60.0"
}
},
"node_modules/@eslint-community/eslint-utils": {

@ -2,7 +2,7 @@
"name": "ai-chat",
"displayName": "AI Chat",
"description": "Open an AI chat webview panel from the status bar",
"version": "0.0.1",
"version": "0.0.5",
"publisher": "your-publisher",
"main": "./out/extension.js",
"engines": {
@ -14,6 +14,26 @@
"onView:ai-chat-sidebar-view"
],
"contributes": {
"configuration": {
"type": "object",
"title": "AI Chat Configuration",
"properties": {
"ai-chat.api.url": {
"type": "string",
"description": "完整的API地址优先级最高"
},
"ai-chat.api.baseUrl": {
"type": "string",
"description": "API基础地址",
"default": "https://p13-ai.ngsk.tech:7001"
},
"ai-chat.api.key": {
"type": "string",
"description": "API密钥",
"default": "dev-token"
}
}
},
"viewsContainers": {
"activitybar": [
{
@ -57,7 +77,8 @@
"compile": "tsc -p ./",
"watch": "tsc -w -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src --ext ts"
"lint": "eslint src --ext ts",
"package": "vsce package"
},
"devDependencies": {
"@types/node": "^16.11.7",

@ -0,0 +1,81 @@
import vscode from "vscode";
import path from "path";
import {getWebviewContent} from "./utils/webView";
import {MessageHandler} from "./MessageHandler";
export class AISidebarViewProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'ai-chat-sidebar-view';
private _view?: vscode.WebviewView;
private _extensionPath: string;
private _context: vscode.ExtensionContext;
private _messageHandler: MessageHandler;
constructor(extensionPath: string, context: vscode.ExtensionContext) {
this._extensionPath = extensionPath;
this._context = context;
this._messageHandler = new MessageHandler(this, context);
}
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
this._view = webviewView;
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, 'media', 'webview'))
]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
webviewView.webview.onDidReceiveMessage(async (message) => {
this._messageHandler.handleMessage(message);
});
}
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);
}
public _postMessage(message: any) {
if (this._view) {
this._view.webview.postMessage(message);
}
}
}

@ -0,0 +1,510 @@
import vscode from "vscode";
import path from "path";
import {callQwenAPI} from "./utils/modelApi";
import {generateCodeDiff, getFileExtension} from "./utils/common";
// 定义全局变量来存储面板状态
let currentSessionHistory: { role: string; content: string }[] = [];
// 存储工作区变更的文件,包含状态信息
let workspaceChanges: {
[key: string]: {
original: string;
modified: string;
status: 'pending' | 'accepted' | 'rejected'
}
} = {};
export class MessageHandler {
private provider: any; // 这里应该使用AISidebarViewProvider类型但由于循环依赖问题暂时使用any
private context: vscode.ExtensionContext;
private currentReader: ReadableStreamDefaultReader<Uint8Array> | null = null; // 添加当前reader引用
constructor(provider: any, context: vscode.ExtensionContext) {
this.provider = provider;
this.context = context;
this.currentReader = null;
}
public async handleMessage(message: any) {
switch (message.command) {
case 'ask':
await this.handleAsk(message);
break;
case 'cancelRequest':
await this.handleCancelRequest();
break;
case 'selectContextFile':
await this.handleSelectContextFile();
break;
case 'getFileList':
await this.handleGetFileList();
break;
case 'selectFileByPath':
await this.handleSelectFileByPath(message);
break;
case 'showCodeDiffInEditor':
await this.handleShowCodeDiffInEditor(message);
break;
case 'acceptChanges':
await this.handleAcceptChanges(message);
break;
case 'rejectChanges':
await this.handleRejectChanges(message);
break;
case 'openWorkspaceFile':
await this.handleOpenWorkspaceFile(message);
break;
case 'getCodeChanges':
this.handleGetCodeChanges();
break;
case 'createNewFile':
await this.handleCreateNewFile(message);
break;
case 'restoreState':
this.handleRestoreState(message);
break;
case 'log' :
console.log( ...message.data);
break;
}
}
private async handleAsk(message: any) {
const question = message.text;
const fileContent = message.fileContent;
const fileName = message.fileContentPath ? path.basename(message.fileContentPath) : "current_file.txt";
const history = currentSessionHistory;
currentSessionHistory = [...history, {role: 'user', content: question}];
this.provider._postMessage({command: 'addMessage', role: 'user', content: question});
try {
const response = await callQwenAPI(question, history, fileContent, this.context, fileName);
// 发送开始流式传输的消息
this.provider._postMessage({command: 'startStream', role: 'assistant'});
// 处理流式响应
const reader = response.body?.getReader();
this.currentReader = reader; // 保存reader引用
const decoder = new TextDecoder('utf-8');
let aiMessage = '';
if (reader) {
try {
while (true) {
const {done, value} = await reader.read();
if (done) break;
const chunk = decoder.decode(value, {stream: true});
// 检查是否包含event: end事件
let processedChunk = chunk;
if (chunk.includes('event: end')) {
// 只处理event: end之前的内容
const endIndex = chunk.indexOf('event: end');
processedChunk = chunk.substring(0, endIndex);
// 处理截取后的内容
if (processedChunk.startsWith('data:')) {
// 移除data:前缀
processedChunk = processedChunk.replace(/^data:/, '');
// 逐个匹配换行符,当出现两个或更多连续换行符时删除多余的
processedChunk = processedChunk.replace(/^(\n{2,})/, (match) => {
// 删除所有连续的换行符
return '';
});
}
if (processedChunk) {
aiMessage += processedChunk;
// 发送流式数据到前端
this.provider._postMessage({
command: 'streamData',
data: processedChunk
});
}
// 停止继续处理
break;
}
if (chunk.startsWith('data:')) {
// 移除data:前缀
processedChunk = chunk.replace(/^data:/, '');
// 逐个匹配换行符,当出现两个或更多连续换行符时删除多余的
processedChunk = processedChunk.replace(/^(\n{2,})/, (match) => {
// 删除所有连续的换行符
return '';
});
}
aiMessage += processedChunk;
// 发送流式数据到前端
this.provider._postMessage({
command: 'streamData',
data: processedChunk
});
}
} catch (readError: any) {
// 检查是否是由于取消请求导致的中断
if (readError.name === 'AbortError' || readError.message.includes('cancel')) {
// 通知前端请求已中断
this.provider._postMessage({command: 'requestCancelled'});
// 重置状态但不抛出错误
this.provider._postMessage({command: 'hideLoading'});
return;
}
// 如果是网络连接中断错误,我们仍然处理已接收的数据
if (readError.message.includes('terminated') || readError.message.includes('other side closed') || readError.name === 'TypeError') {
// 继续处理已接收的数据
console.log("网络连接中断,但将继续处理已接收的数据");
aiMessage += "```"
} else {
// 如果是其他错误,重新抛出
throw readError;
}
} finally {
// 清理reader引用
this.currentReader = null;
}
// console.log("aiMessage:",aiMessage)
// 流传输完成后,更新会话历史
currentSessionHistory = [...currentSessionHistory, {role: 'assistant', content: aiMessage}];
// 解析AI响应中的多个代码块并生成独立的codeDiff信息
const codeBlocks = aiMessage.match(/```[\s\S]*?```/g) || [];
// console.log("codeBlocks:",codeBlocks)
// 存储所有生成的codeDiff
const allCodeDiffs = [];
for (const block of codeBlocks) {
// 生成codeDiff
const codeDiff = generateCodeDiff(fileContent, block);
if (codeDiff) {
// @ts-ignore
allCodeDiffs.push(codeDiff);
}
}
// console.log("allCodeDiffs:",allCodeDiffs)
// 通知前端流传输完成
this.provider._postMessage({
command: 'endStream',
content: aiMessage,
codeDiffs: allCodeDiffs // 传递多个codeDiff信息
});
// 如果有代码差异,保存到工作区变更中
if (allCodeDiffs.length > 0 && fileContent && message.fileContentPath) {
// 为每个codeDiff创建独立的workspaceChange
for (let i = 0; i < allCodeDiffs.length; i++) {
const codeDiff = allCodeDiffs[i];
const filePath = message.fileContentPath;
// 创建唯一的工作区变更键
const changeKey = `${filePath}`;
workspaceChanges[changeKey] = {
original: fileContent,
// @ts-ignore
modified: codeDiff.modifiedCode,
status: 'pending'
};
}
// 通知 WebView 更新工作区文件列表
this.provider._postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
}
}
} catch (error: any) {
console.log("error:",error)
// 检查是否是由于取消请求导致的中断
if (error.name === 'AbortError' || error.message.includes('cancel') || error.message.includes('中断')) {
this.provider._postMessage({command: 'requestCancelled'});
} else if (error.message.includes('terminated') || error.message.includes('other side closed') || error.name === 'TypeError') {
// 处理网络连接中断错误,但仍显示已接收的内容
this.provider._postMessage({command: 'addMessage', role: 'ai', content: '网络连接中断,但以下为已接收的内容:'});
// 如果有已接收的内容,也显示出来
// 通知前端流传输完成
this.provider._postMessage({
command: 'endStream',
content: '',
codeDiffs: []
});
} else {
this.provider._postMessage({command: 'addMessage', role: 'ai', content: '调用失败:' + error.message});
}
}
this.provider._postMessage({command: 'hideLoading'});
}
private async handleSelectContextFile() {
// 执行文件选择逻辑
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);
this.provider._postMessage({
command: 'setContextFile',
fileName: selectedFileUri.fsPath,
fileContent: decodedContent
});
}
}
private async handleGetFileList() {
// 执行获取文件列表逻辑
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.provider._postMessage({
command: 'fileList',
files: files
});
}
}
private async handleSelectFileByPath(message: any) {
// 根据路径选择文件
try {
const fileUri = vscode.Uri.file(message.filePath);
const fileContent = await vscode.workspace.fs.readFile(fileUri);
const decodedContent = new TextDecoder("utf-8").decode(fileContent);
this.provider._postMessage({
command: 'setContextFile',
fileName: fileUri.fsPath,
fileContent: decodedContent
});
} catch (error: any) {
vscode.window.showErrorMessage(`无法读取文件: ${error.message}`);
}
}
private async handleShowCodeDiffInEditor(message: any) {
// 在编辑器中显示代码差异
try {
const originalCode = message.originalCode || '';
const modifiedCode = message.modifiedCode || '';
// 创建原始代码的虚拟文档
const originalUri = vscode.Uri.parse('untitled:original-code.txt');
const originalDoc = await vscode.workspace.openTextDocument(originalUri);
const originalEditor = await vscode.window.showTextDocument(originalDoc, vscode.ViewColumn.One, true);
await originalEditor.edit(edit => {
edit.insert(new vscode.Position(0, 0), originalCode);
});
// 创建修改后代码的虚拟文档
const modifiedUri = vscode.Uri.parse('untitled:modified-code.txt');
const modifiedDoc = await vscode.workspace.openTextDocument(modifiedUri);
const modifiedEditor = await vscode.window.showTextDocument(modifiedDoc, vscode.ViewColumn.Two, true);
await modifiedEditor.edit(edit => {
edit.insert(new vscode.Position(0, 0), modifiedCode);
});
// 使用 VS Code 的 diff 命令显示差异
await vscode.commands.executeCommand(
'vscode.diff',
originalUri,
modifiedUri,
'Code Diff - Original ↔ Modified'
);
} catch (error: any) {
vscode.window.showErrorMessage(`显示代码差异失败: ${error.message}`);
}
}
private async handleAcceptChanges(message: any) {
// 接受代码变更
try {
const {filePath, modifiedContent} = message;
const fileUri = vscode.Uri.file(filePath);
// 将修改后的内容写入文件
const encodedContent = new TextEncoder().encode(modifiedContent);
await vscode.workspace.fs.writeFile(fileUri, encodedContent);
// 更新工作区变更状态
if (workspaceChanges[filePath]) {
workspaceChanges[filePath].status = 'accepted';
}
// 通知 WebView 更新工作区文件列表
this.provider._postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
vscode.window.showInformationMessage('代码变更已应用');
} catch (error: any) {
vscode.window.showErrorMessage(`应用代码变更失败: ${error.message}`);
}
}
private async handleRejectChanges(message: any) {
// 拒绝代码变更
try {
const {filePath} = message;
// 更新工作区变更状态
if (workspaceChanges[filePath]) {
workspaceChanges[filePath].status = 'rejected';
}
// 通知 WebView 更新工作区文件列表
this.provider._postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
vscode.window.showInformationMessage('代码变更已拒绝');
} catch (error: any) {
vscode.window.showErrorMessage(`拒绝代码变更失败: ${error.message}`);
}
}
private async handleOpenWorkspaceFile(message: any) {
// 打开工作区文件
try {
const {filePath} = message;
const fileUri = vscode.Uri.file(filePath);
// 检查文件是否已经打开
let fileAlreadyOpen = false;
const openedTextDocuments = vscode.workspace.textDocuments;
for (const doc of openedTextDocuments) {
if (doc.uri.fsPath === fileUri.fsPath) {
fileAlreadyOpen = true;
// 激活已打开的文件
await vscode.window.showTextDocument(doc, {
viewColumn: vscode.ViewColumn.One // 切换到已打开的文件
});
break;
}
}
// 如果文件未打开,则在新窗口打开
if (!fileAlreadyOpen) {
await vscode.window.showTextDocument(fileUri, {
viewColumn: vscode.ViewColumn.One // 在第一列打开文件
});
}
} catch (error: any) {
vscode.window.showErrorMessage(`打开文件失败: ${error.message}`);
}
}
private handleGetCodeChanges() {
// 获取代码变更
this.provider._postMessage({
command: 'codeChanges',
changes: workspaceChanges
});
}
private async handleCreateNewFile(message: any) {
// 创建新文件功能
try {
const {fileName, content, language} = message;
// 获取根工作区路径
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
vscode.window.showErrorMessage('未找到工作区');
return;
}
// 根据语言确定文件扩展名
const fileExtension = getFileExtension(language);
const fullFileName = fileName.endsWith(fileExtension) ? fileName : `${fileName}${fileExtension}`;
// 创建文件路径(在根目录下)
const filePath = path.join(workspaceFolders[0].uri.fsPath, fullFileName);
const fileUri = vscode.Uri.file(filePath);
// 将内容写入文件
const encodedContent = new TextEncoder().encode(content);
await vscode.workspace.fs.writeFile(fileUri, encodedContent);
// 在编辑器中打开新创建的文件
await vscode.window.showTextDocument(fileUri, {
viewColumn: vscode.ViewColumn.One
});
vscode.window.showInformationMessage(`文件 ${fullFileName} 已创建`);
} catch (error: any) {
vscode.window.showErrorMessage(`生成代码文件失败: ${error.message}`);
}
}
private handleRestoreState(message: any) {
// 恢复面板状态
currentSessionHistory = message.history || [];
// 恢复显示历史消息
currentSessionHistory.forEach(msg => {
this.provider._postMessage({
command: 'addMessage',
role: msg.role,
content: msg.content,
codeDiff: null
});
});
// 恢复工作区变更
if (message.workspaceChanges) {
this.provider._postMessage({
command: 'updateWorkspaceFiles',
files: message.workspaceChanges
});
}
}
// 添加处理取消请求的方法
private async handleCancelRequest() {
if (this.currentReader) {
try {
await this.currentReader.cancel();
this.currentReader = null;
console.log('请求已取消');
} catch (error) {
console.error('取消请求时出错:', error);
}
}
}
}

@ -1,18 +1,5 @@
import * as vscode from 'vscode';
import * as path from 'path';
import {callQwenAPI} from "./utils/modelApi";
import {generateCodeDiff,getFileExtension} from "./utils/common";
// 定义全局变量来存储面板状态
let currentSessionHistory: { role: string; content: string }[] = [];
// 存储工作区变更的文件,包含状态信息
let workspaceChanges: {
[key: string]: {
original: string;
modified: string;
status: 'pending' | 'accepted' | 'rejected'
}
} = {};
import {AISidebarViewProvider} from "./AISidebarViewProvider"
export function activate(context: vscode.ExtensionContext) {
console.log('Extension activated');
@ -64,448 +51,3 @@ export function activate(context: vscode.ExtensionContext) {
// 添加到 subscriptions
context.subscriptions.push(statusBarItem, openWebviewCommand, addToChatCommand);
}
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;
}
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
this._view = webviewView;
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, 'media', 'webview'))
]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
webviewView.webview.onDidReceiveMessage(async (message) => {
switch (message.command) {
case 'ask':
const question = message.text;
const fileContent = message.fileContent;
const history = currentSessionHistory;
currentSessionHistory = [...history, { role: 'user', content: question }];
this._postMessage({ command: 'addMessage', role: 'user', content: question });
try {
const response = await callQwenAPI(question, history, fileContent, this._context);
console.log("response:", response);
const aiMessage = response.choices[0]?.message?.content || '未获取到回复';
// 检查是否包含代码修改
const codeDiff = generateCodeDiff(fileContent, aiMessage);
console.log("codeDiff:",codeDiff)
// 如果有代码差异,保存到工作区变更中
if (codeDiff && fileContent && message.fileContentPath) {
workspaceChanges[message.fileContentPath] = {
original: fileContent,
modified: codeDiff.modifiedCode,
status: 'pending'
};
// 通知 WebView 更新工作区文件列表
this._postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
}
// 更新当前会话历史
currentSessionHistory = [...currentSessionHistory, { role: 'assistant', content: aiMessage }];
this._postMessage({
command: 'addMessage',
role: 'ai',
content: aiMessage,
codeDiff: codeDiff // 发送代码差异信息
});
} catch (error: any) {
this._postMessage({ command: 'addMessage', role: 'ai', content: '调用失败:' + error.message });
}
this._postMessage({ command: 'hideLoading' });
break;
case 'selectContextFile':
// 执行文件选择逻辑
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);
this._postMessage({
command: 'setContextFile',
fileName: selectedFileUri.fsPath,
fileContent: decodedContent
});
}
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 // 简化处理,实际可以根据扩展名判断
};
});
this._postMessage({
command: 'fileList',
files: files
});
}
break;
case 'selectFileByPath':
// 根据路径选择文件
try {
const fileUri = vscode.Uri.file(message.filePath);
const fileContent = await vscode.workspace.fs.readFile(fileUri);
const decodedContent = new TextDecoder("utf-8").decode(fileContent);
this._postMessage({
command: 'setContextFile',
fileName: fileUri.fsPath,
fileContent: decodedContent
});
} catch (error: any) {
vscode.window.showErrorMessage(`无法读取文件: ${error.message}`);
}
break;
case 'showCodeDiffInEditor':
// 在编辑器中显示代码差异
try {
const originalCode = message.originalCode || '';
const modifiedCode = message.modifiedCode || '';
// 创建原始代码的虚拟文档
const originalUri = vscode.Uri.parse('untitled:original-code.txt');
const originalDoc = await vscode.workspace.openTextDocument(originalUri);
const originalEditor = await vscode.window.showTextDocument(originalDoc, vscode.ViewColumn.One, true);
await originalEditor.edit(edit => {
edit.insert(new vscode.Position(0, 0), originalCode);
});
// 创建修改后代码的虚拟文档
const modifiedUri = vscode.Uri.parse('untitled:modified-code.txt');
const modifiedDoc = await vscode.workspace.openTextDocument(modifiedUri);
const modifiedEditor = await vscode.window.showTextDocument(modifiedDoc, vscode.ViewColumn.Two, true);
await modifiedEditor.edit(edit => {
edit.insert(new vscode.Position(0, 0), modifiedCode);
});
// 使用 VS Code 的 diff 命令显示差异
await vscode.commands.executeCommand(
'vscode.diff',
originalUri,
modifiedUri,
'Code Diff - Original ↔ Modified'
);
} catch (error: any) {
vscode.window.showErrorMessage(`显示代码差异失败: ${error.message}`);
}
break;
case 'acceptChanges':
// 接受代码变更
try {
const { filePath, modifiedContent } = message;
const fileUri = vscode.Uri.file(filePath);
// 将修改后的内容写入文件
const encodedContent = new TextEncoder().encode(modifiedContent);
await vscode.workspace.fs.writeFile(fileUri, encodedContent);
// 更新工作区变更状态
if (workspaceChanges[filePath]) {
workspaceChanges[filePath].status = 'accepted';
}
// 通知 WebView 更新工作区文件列表
this._postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
vscode.window.showInformationMessage('代码变更已应用');
} catch (error: any) {
vscode.window.showErrorMessage(`应用代码变更失败: ${error.message}`);
}
break;
case 'rejectChanges':
// 拒绝代码变更
try {
const { filePath } = message;
// 更新工作区变更状态
if (workspaceChanges[filePath]) {
workspaceChanges[filePath].status = 'rejected';
}
// 通知 WebView 更新工作区文件列表
this._postMessage({
command: 'updateWorkspaceFiles',
files: workspaceChanges
});
vscode.window.showInformationMessage('代码变更已拒绝');
} catch (error: any) {
vscode.window.showErrorMessage(`拒绝代码变更失败: ${error.message}`);
}
break;
case 'openWorkspaceFile':
// 打开工作区文件
try {
const { filePath } = message;
const fileUri = vscode.Uri.file(filePath);
// 检查文件是否已经打开
let fileAlreadyOpen = false;
const openedTextDocuments = vscode.workspace.textDocuments;
for (const doc of openedTextDocuments) {
if (doc.uri.fsPath === fileUri.fsPath) {
fileAlreadyOpen = true;
// 激活已打开的文件
await vscode.window.showTextDocument(doc, {
viewColumn: vscode.ViewColumn.One // 切换到已打开的文件
});
break;
}
}
// 如果文件未打开,则在新窗口打开
if (!fileAlreadyOpen) {
await vscode.window.showTextDocument(fileUri, {
viewColumn: vscode.ViewColumn.One // 在第一列打开文件
});
}
} catch (error: any) {
vscode.window.showErrorMessage(`打开文件失败: ${error.message}`);
}
break;
case 'getCodeChanges':
// 获取代码变更
this._postMessage({
command: 'codeChanges',
changes: workspaceChanges
});
break;
case 'createNewFile':
// 创建新文件功能
try {
const { fileName, content, language } = message;
// 获取根工作区路径
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
vscode.window.showErrorMessage('未找到工作区');
return;
}
// 根据语言确定文件扩展名
const fileExtension = getFileExtension(language);
const fullFileName = fileName.endsWith(fileExtension) ? fileName : `${fileName}${fileExtension}`;
// 创建文件路径(在根目录下)
const filePath = path.join(workspaceFolders[0].uri.fsPath, fullFileName);
const fileUri = vscode.Uri.file(filePath);
// 将内容写入文件
const encodedContent = new TextEncoder().encode(content);
await vscode.workspace.fs.writeFile(fileUri, encodedContent);
// 在编辑器中打开新创建的文件
await vscode.window.showTextDocument(fileUri, {
viewColumn: vscode.ViewColumn.One
});
vscode.window.showInformationMessage(`文件 ${fullFileName} 已创建`);
} catch (error: any) {
vscode.window.showErrorMessage(`生成代码文件失败: ${error.message}`);
}
break;
case 'restoreState':
// 恢复面板状态
currentSessionHistory = message.history || [];
// 恢复显示历史消息
currentSessionHistory.forEach(msg => {
this._postMessage({
command: 'addMessage',
role: msg.role,
content: msg.content,
codeDiff: null
});
});
// 恢复工作区变更
if (message.workspaceChanges) {
this._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);
}
private _postMessage(message: any) {
if (this._view) {
this._view.webview.postMessage(message);
}
}
}
function getWebviewContent(styleUri: vscode.Uri, scriptUri: vscode.Uri,highlightScriptUri:vscode.Uri,highlightStyleUri:vscode.Uri,markedScriptUri:vscode.Uri): string {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AI Chat</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="${styleUri}" />
<link href="${highlightStyleUri}" rel="stylesheet" />
</head>
<body>
<div class="chat-container">
<!---->
<div id="chat-box"></div>
<!---->
<div class="context-container">
<button id="select-context-btn" class="select-context-btn" title="选择上下文文件">+</button>
<div id="context-placeholder" class="context-placeholder">
<span class="placeholder-text"></span>
</div>
<div id="context-tab" class="context-tab hidden">
<span id="context-file-name" class="file-name"></span>
<button id="close-context-btn" class="close-btn" title="清除上下文">×</button>
</div>
</div>
<!-- -->
<div class="workspace-container" style="display: none;">
<h3></h3>
<div id="workspace-files" class="workspace-files">
<div class="no-changes"></div>
</div>
</div>
<!-- -->
<div id="file-browser-modal" class="modal hidden">
<div class="modal-content">
<div class="modal-header">
<h3></h3>
<span id="close-modal" class="close-modal">&times;</span>
</div>
<div class="modal-body">
<div class="file-search">
<input type="text" id="file-search-input" placeholder="搜索文件..." />
</div>
<div id="file-list" class="file-list">
<div class="loading">...</div>
</div>
</div>
</div>
</div>
<!-- -->
<div id="diff-modal" class="modal hidden">
<div class="modal-content diff-modal-content">
<div class="modal-header">
<h3></h3>
<span id="close-diff-modal" class="close-modal">&times;</span>
</div>
<div class="modal-body">
<div id="diff-content" class="diff-content"></div>
<div class="diff-actions">
<button id="accept-changes-btn" class="diff-action-btn accept"></button>
<button id="reject-changes-btn" class="diff-action-btn reject"></button>
</div>
</div>
</div>
</div>
<!-- -->
<form id="chat-form">
<input type="text" id="user-input" placeholder="输入你的问题..." required />
<button type="submit"></button>
</form>
<div id="loading" class="hidden">AI ...</div>
</div>
<script src="${scriptUri}"></script>
<script src="${highlightScriptUri}"></script>
<script src="${markedScriptUri}"></script>
</body>
</html>`;
}

@ -1,13 +1,80 @@
import vscode from "vscode";
import path from "path";
// 获取API配置的函数
function getApiConfig(context: vscode.ExtensionContext) {
// 从用户配置中获取设置
const config = vscode.workspace.getConfiguration('ai-chat.api');
const userUrl = config.get<string>('url');
const userBaseUrl = config.get<string>('baseUrl');
const userApiKey = config.get<string>('key');
// 如果用户配置了完整URL优先使用
if (userUrl) {
return {
url: userUrl,
apiKey: userApiKey || 'dev-token'
};
}
// 如果用户配置了基础URL使用基础URL加上默认路径
if (userBaseUrl) {
return {
url: `${userBaseUrl}/comp/api/v1/chat/completions-stream`,
apiKey: userApiKey || 'dev-token'
};
}
// 检测code-server环境
const codeServerUrl = process.env.CODE_SERVER_URL;
if (codeServerUrl) {
// 在code-server环境中尝试使用相对路径或者基于当前地址的API地址
try {
const baseUrl = new URL(codeServerUrl);
return {
url: `${baseUrl.origin}/comp/api/v1/chat/completions-stream`,
apiKey: userApiKey || 'dev-token'
};
} catch (e) {
// 如果解析失败,使用默认地址
}
}
// 检查CODE_SERVER_CONFIG环境变量
const codeServerConfig = process.env.CODE_SERVER_CONFIG;
if (codeServerConfig) {
try {
// 尝试从配置中解析bindAddr
const configObj = JSON.parse(codeServerConfig);
if (configObj.bindAddr) {
const [host, port] = configObj.bindAddr.split(':');
if (host && port) {
return {
url: `http://${host}:${port}/comp/api/v1/chat/completions-stream`,
apiKey: userApiKey || 'dev-token'
};
}
}
} catch (e) {
// 解析失败则继续使用默认配置
}
}
// 默认配置
return {
url: 'https://p13-ai.ngsk.tech:7001/comp/api/v1/chat/completions-stream',
apiKey: userApiKey || 'dev-token'
};
}
export async function callQwenAPI(
question: string,
history: any[],
fileContent: string,
context: vscode.ExtensionContext
context: vscode.ExtensionContext,
filename: string = ""
): Promise<any> {
const apiKey = 'dev-token';
const url = 'https://aicomp.ngsk.tech:7001/api/v1/chat/completions';
const { url, apiKey } = getApiConfig(context);
const messages = [
{
@ -17,36 +84,49 @@ export async function callQwenAPI(
...history,
{
role: 'user',
content: `【当前文件内容】:
\`\`\`${fileContent}\`\`\`
:
${question}`
content: question
}
];
console.log("messages:", messages)
try {
const response = await fetch(url, {
const params = {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
// 'Accept': 'text/event-stream'
},
body: JSON.stringify({
model: 'qwen2.5-local',
messages
model: 'Qwen2_5_Coder',
messages,
context_file: {
filename: filename,
section_type: "code",
content: fileContent
}
})
});
}
console.log("params:", JSON.stringify(params))
// @ts-ignore
const response = await fetch(url, params);
if (!response.ok) {
throw new Error('API 请求失败');
console.log("请求失败:", response)
throw new Error(`API 请求失败: ${response.status} ${response.statusText}`);
}
return await response.json();
return response;
} catch (error: any) {
console.error(error);
// 提供更具体的错误信息
if (error.message.includes('terminated') || error.message.includes('other side closed')) {
throw new Error('网络连接中断,请检查网络状况或稍后重试。');
} else if (error.name === 'TypeError') {
throw new Error(`网络错误: ${error.message}`);
}
throw error;
}
}

@ -1,3 +1,7 @@
import vscode from "vscode";
export function getWebviewContent(styleUri: vscode.Uri, scriptUri: vscode.Uri,highlightScriptUri:vscode.Uri,highlightStyleUri:vscode.Uri,markedScriptUri:vscode.Uri): string {
return `
<!DOCTYPE html>
<html lang="en">
<head>
@ -27,7 +31,7 @@
</div>
<!-- -->
<div class="workspace-container">
<div class="workspace-container" style="display: none;">
<h3></h3>
<div id="workspace-files" class="workspace-files">
<div class="no-changes"></div>
@ -71,8 +75,8 @@
<!-- -->
<form id="chat-form">
<input type="text" id="user-input" placeholder="输入你的问题..." required />
<button type="submit"></button>
<input type="text" id="user-input" placeholder="输入你的问题..."/>
<button type="submit" id="send-btn"></button>
</form>
<div id="loading" class="hidden">AI ...</div>
</div>
@ -80,4 +84,5 @@
<script src="${highlightScriptUri}"></script>
<script src="${markedScriptUri}"></script>
</body>
</html>
</html>`;
}
Loading…
Cancel
Save