Skip to content

Commit a633dcb

Browse files
committed
修复部分BUG,增加MD解析,增加MD代码复制
1 parent 1a4b561 commit a633dcb

6 files changed

Lines changed: 9637 additions & 72 deletions

File tree

iframe/main/index.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<li><button id="setting-btn">切换主题</button></li>
2323
<li><button id="SaveinLeft-btn">保存到列表</button></li>
2424
<li><button id="Ext-btn">插件管理</button></li>
25-
<li><button id="ai-btn">AI编程:关</button></li>
25+
<li><button id="ai-btn">AI编程</button></li>
2626
<!-- <li><button id="example-btn" style="display: none;">案例</button></li>
2727
<li><button id="about-btn" style="display: none;">关于</button></li> -->
2828
</ul>
@@ -97,6 +97,11 @@
9797
<script src="/iframe/script/SweetAlert2/sweetalert2@11.js"></script>
9898
<!-- 工具类 -->
9999
<script src="/iframe/script/eext_tool/jszip.min.js"></script>
100+
<!-- 文件解压 -->
101+
<script src="/iframe/script/eext_tool/highlight.min.js"></script>
102+
<!-- AI语法高亮 -->
103+
<script src="/iframe/script/eext_tool/marked.min.js"></script>
104+
<!-- MD语法解析 -->
100105
<script>
101106
//初始化Ace 编辑器
102107
const settingsMenu = ace.require('ace/ext/settings_menu');
@@ -124,6 +129,9 @@
124129
const dark_theme = document.getElementById('theme-dark');
125130
GetTheme(editor, light_theme, dark_theme);
126131

132+
//获取按钮状态
133+
GetVibeCodingConfig();
134+
127135
//扫描插件并执行
128136
ExtStore_LoadAndRunAllPlugins();
129137
//加载按钮到左侧

iframe/script/User_config/Ai_Chat.js

Lines changed: 120 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,63 @@
11
async function SetVibeCodingConfig() {
22
const flag = await eda.sys_Storage.getExtensionUserConfig('Vibe_Coding_Config');
33
if (flag == 'true') {
4-
document.getElementById('ai-chat').style.display = '';
5-
document.getElementById('ai-btn').innerText = 'AI编程:开';
64
eda.sys_Storage.setExtensionUserConfig('Vibe_Coding_Config', 'false');
5+
const chatEl = document.getElementById('ai-chat');
6+
if (chatEl) chatEl.style.display = 'none';
7+
const btn = document.getElementById('ai-btn');
8+
if (btn) btn.innerText = 'AI编程:关';
79
} else {
8-
document.getElementById('ai-chat').style.display = 'none';
9-
document.getElementById('ai-btn').innerText = 'AI编程:关';
1010
eda.sys_Storage.setExtensionUserConfig('Vibe_Coding_Config', 'true');
11+
const chatEl = document.getElementById('ai-chat');
12+
if (chatEl) chatEl.style.display = '';
13+
const btn = document.getElementById('ai-btn');
14+
if (btn) btn.innerText = 'AI编程:开';
15+
}
16+
}
17+
18+
async function GetVibeCodingConfig() {
19+
const flag = await eda.sys_Storage.getExtensionUserConfig('Vibe_Coding_Config');
20+
const btn = document.getElementById('ai-btn');
21+
if (!btn) return;
22+
23+
const chatEl = document.getElementById('ai-chat');
24+
25+
if (flag == 'true') {
26+
btn.innerText = 'AI编程:开';
27+
if (chatEl) chatEl.style.display = '';
28+
} else {
29+
btn.innerText = 'AI编程:关';
30+
if (chatEl) chatEl.style.display = 'none';
1131
}
1232
}
1333

1434
function initAiChat() {
35+
// 检查依赖库
36+
if (typeof marked === 'undefined' || typeof hljs === 'undefined') {
37+
console.error('AI Chat Init Error: marked or highlight.js not found.');
38+
} else {
39+
marked.setOptions({
40+
highlight: function (code, lang) {
41+
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
42+
return hljs.highlight(code, { language }).value;
43+
},
44+
langPrefix: 'hljs language-',
45+
breaks: true,
46+
gfm: true,
47+
});
48+
}
49+
1550
const defaultConfig = {
1651
apiKey: '',
1752
baseUrl: 'https://api.openai.com/v1',
1853
model: 'gpt-4',
1954
multiTurn: true,
20-
stream: true, // 控制是否开启“打字机效果”
55+
stream: true,
2156
};
2257

2358
let chatConfig = JSON.parse(localStorage.getItem('ai_chat_config')) || defaultConfig;
2459
let messageHistory = [];
2560

26-
// DOM 元素
2761
const settingsBtn = document.getElementById('ai-settings-trigger');
2862
const sendBtn = document.getElementById('ai-send-btn');
2963
const inputArea = document.getElementById('ai-input');
@@ -32,6 +66,51 @@ function initAiChat() {
3266

3367
if (modelNameDisplay) modelNameDisplay.textContent = chatConfig.model || 'AI Model';
3468

69+
// --- 新增:添加复制按钮的逻辑 ---
70+
function addCopyButtons(container) {
71+
if (!container) return;
72+
73+
const pres = container.querySelectorAll('pre');
74+
pres.forEach((pre) => {
75+
// 如果已经添加过按钮,跳过
76+
if (pre.querySelector('.code-copy-btn')) return;
77+
78+
const codeElement = pre.querySelector('code');
79+
if (!codeElement) return;
80+
81+
const btn = document.createElement('button');
82+
btn.className = 'code-copy-btn';
83+
btn.innerHTML = `
84+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
85+
<span>复制</span>
86+
`;
87+
88+
btn.onclick = async () => {
89+
const text = codeElement.innerText;
90+
try {
91+
await navigator.clipboard.writeText(text);
92+
// 更改按钮状态
93+
const originalContent = btn.innerHTML;
94+
btn.classList.add('copied');
95+
btn.innerHTML = `
96+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
97+
<span>已复制</span>
98+
`;
99+
100+
setTimeout(() => {
101+
btn.classList.remove('copied');
102+
btn.innerHTML = originalContent;
103+
}, 2000);
104+
} catch (err) {
105+
console.error('复制失败:', err);
106+
alert('复制失败,请手动复制');
107+
}
108+
};
109+
110+
pre.appendChild(btn);
111+
});
112+
}
113+
35114
// --- 模态框创建 ---
36115
function createModal() {
37116
const overlay = document.createElement('div');
@@ -58,7 +137,7 @@ function initAiChat() {
58137
<label class="ai-form-label">Model Name</label>
59138
<input type="text" id="cfg-model" class="ai-form-input" value="${chatConfig.model}">
60139
</div>
61-
<hr style="border: 0; border-top: 1px solid #3e3d32; margin: 4px 0;">
140+
<hr style="border: 0; border-top: 1px solid #d0d7de; margin: 4px 0;">
62141
<div class="ai-toggle-row">
63142
<span class="ai-toggle-label">多轮对话</span>
64143
<label class="ai-switch"><input type="checkbox" id="cfg-multi-turn" ${chatConfig.multiTurn ? 'checked' : ''}><span class="ai-slider"></span></label>
@@ -89,7 +168,6 @@ function initAiChat() {
89168
document.getElementById('cfg-model').value = chatConfig.model;
90169
document.getElementById('cfg-multi-turn').checked = chatConfig.multiTurn;
91170
document.getElementById('cfg-stream').checked = chatConfig.stream;
92-
93171
setTimeout(() => modalOverlay.classList.add('active'), 10);
94172
}
95173

@@ -109,7 +187,6 @@ function initAiChat() {
109187
alert('请填写 API Key');
110188
return;
111189
}
112-
113190
chatConfig = newConfig;
114191
localStorage.setItem('ai_chat_config', JSON.stringify(chatConfig));
115192
if (!chatConfig.multiTurn) messageHistory = [];
@@ -134,23 +211,32 @@ function initAiChat() {
134211
function appendMessage(role, text, isStreaming = false, isLoading = false) {
135212
const msgDiv = document.createElement('div');
136213
msgDiv.className = `ai-message ${role === 'user' ? 'ai-message-user' : 'ai-message-system'}`;
137-
138214
const bubble = document.createElement('div');
139215
bubble.className = 'ai-message-bubble';
140216

141217
if (isLoading) {
142-
// 加载状态:添加特定的 ID 和 loading 类
143218
msgDiv.id = 'ai-loading-message';
144219
bubble.classList.add('ai-loading-bubble');
145-
// 内部放入三个跳动的点 (HTML 结构)
146220
bubble.innerHTML = '<span class="dot">.</span><span class="dot">.</span><span class="dot">.</span>';
147221
} else if (isStreaming) {
148222
bubble.id = 'streaming-bubble-' + Date.now();
149-
bubble.textContent = '';
150-
bubble.style.borderRight = '2px solid #66d9ef';
151-
bubble.style.animation = 'blink 1s step-end infinite';
152-
} else {
153223
bubble.textContent = text;
224+
bubble.classList.add('streaming-cursor');
225+
bubble.style.whiteSpace = 'pre-wrap';
226+
} else {
227+
if (role === 'system' && typeof marked !== 'undefined') {
228+
bubble.innerHTML = marked.parse(text);
229+
if (typeof hljs !== 'undefined') {
230+
bubble.querySelectorAll('pre code').forEach((block) => {
231+
hljs.highlightElement(block);
232+
});
233+
}
234+
// 渲染完成后添加复制按钮
235+
addCopyButtons(bubble);
236+
} else {
237+
bubble.textContent = text;
238+
bubble.style.whiteSpace = 'pre-wrap';
239+
}
154240
}
155241

156242
msgDiv.appendChild(bubble);
@@ -170,28 +256,22 @@ function initAiChat() {
170256
return;
171257
}
172258

173-
// 1. UI: 用户消息
174259
appendMessage('user', text);
175260
inputArea.value = '';
176261
inputArea.style.height = 'auto';
177262

178-
// 2. UI: 显示 "AI 正在思考..." 加载气泡
179263
const loadingElements = appendMessage('system', '', false, true);
180-
181-
// 准备历史
182264
const currentMessages = [...messageHistory, { role: 'user', content: text }];
183265

184266
let aiBubble = null;
185267
let fullResponse = '';
186268
const originalBtnText = sendBtn.textContent;
187269

188-
// UI: 锁定按钮
189270
sendBtn.disabled = true;
190271
sendBtn.textContent = '...';
191272
sendBtn.style.opacity = '0.6';
192273

193274
try {
194-
// 3. 发起请求 (始终非流式)
195275
const response = await fetch(`${chatConfig.baseUrl}/chat/completions`, {
196276
method: 'POST',
197277
headers: {
@@ -214,36 +294,44 @@ function initAiChat() {
214294
const data = await response.json();
215295
fullResponse = data.choices?.[0]?.message?.content || '无内容';
216296

217-
// 4. 移除加载气泡
218297
if (loadingElements && loadingElements.msgDiv) {
219298
loadingElements.msgDiv.remove();
220299
}
221300

222-
// 5. 根据配置显示内容
223301
if (chatConfig.stream) {
224-
// === 打字机模式 ===
225302
const streamElements = appendMessage('system', '', true);
226303
aiBubble = streamElements.bubble;
227304

228305
let currentIndex = 0;
229-
const speed = 20;
306+
const speed = 15;
230307

231308
const typeWriter = () => {
232309
if (currentIndex < fullResponse.length) {
233-
aiBubble.textContent += fullResponse.charAt(currentIndex);
310+
const currentText = fullResponse.slice(0, currentIndex + 1);
311+
aiBubble.textContent = currentText;
234312
currentIndex++;
235313
chatList.scrollTop = chatList.scrollHeight;
236314
setTimeout(typeWriter, speed);
237315
} else {
238-
// 结束
239-
aiBubble.style.borderRight = 'none';
240-
aiBubble.style.animation = 'none';
316+
aiBubble.classList.remove('streaming-cursor');
317+
318+
if (typeof marked !== 'undefined') {
319+
aiBubble.innerHTML = marked.parse(fullResponse);
320+
321+
if (typeof hljs !== 'undefined') {
322+
aiBubble.querySelectorAll('pre code').forEach((block) => {
323+
hljs.highlightElement(block);
324+
});
325+
}
326+
// 打字结束后添加复制按钮
327+
addCopyButtons(aiBubble);
328+
}
329+
chatList.scrollTop = chatList.scrollHeight;
241330
finishProcess();
242331
}
243332
};
244333
typeWriter();
245334
} else {
246-
// === 直接显示模式 ===
247335
appendMessage('system', fullResponse);
248336
finishProcess();
249337
}
@@ -261,20 +349,18 @@ function initAiChat() {
261349
}
262350
} catch (error) {
263351
console.error(error);
264-
// 移除加载气泡
265352
if (loadingElements && loadingElements.msgDiv) {
266353
loadingElements.msgDiv.remove();
267354
}
268355

269-
// 显示错误
270356
const isDark = getComputedStyle(document.body).backgroundColor === 'rgb(39, 40, 34)';
271357
const errStyle = isDark
272358
? `background:#5a2d2d; border-color:#8b3a3a; color:#ffaaaa;`
273359
: `background:#ffeef0; border-color:#fdaeb7; color:#cf222e;`;
274360

275361
const errDiv = document.createElement('div');
276362
errDiv.className = 'ai-message ai-message-system';
277-
errDiv.innerHTML = `<div class="ai-message-bubble" style="${errStyle}">❌ 错误: ${error.message}</div>`;
363+
errDiv.innerHTML = `<div class="ai-message-bubble" style="${errStyle}">❌ 错误${error.message}</div>`;
278364
chatList.appendChild(errDiv);
279365
chatList.scrollTop = chatList.scrollHeight;
280366

@@ -284,7 +370,6 @@ function initAiChat() {
284370
}
285371
}
286372

287-
// 初始化事件
288373
if (settingsBtn) settingsBtn.onclick = openModal;
289374
if (sendBtn) sendBtn.onclick = handleSend;
290375

@@ -301,40 +386,6 @@ function initAiChat() {
301386
}
302387
};
303388
}
304-
305-
// 注入样式 (Blink 动画 + Loading 跳动动画)
306-
if (!document.getElementById('ai-chat-animations')) {
307-
const s = document.createElement('style');
308-
s.id = 'ai-chat-animations';
309-
s.textContent = `
310-
@keyframes blink { 50% { border-color: transparent; } }
311-
312-
/* 加载跳动动画 */
313-
.ai-loading-bubble {
314-
display: flex;
315-
gap: 4px;
316-
padding: 12px 16px; /* 稍微调整内边距以适应圆点 */
317-
align-items: center;
318-
justify-content: center;
319-
min-width: 60px;
320-
}
321-
.ai-loading-bubble .dot {
322-
font-size: 20px;
323-
line-height: 1;
324-
color: inherit; /* 继承气泡文字颜色 */
325-
opacity: 0.6;
326-
animation: bounce 1.4s infinite ease-in-out both;
327-
}
328-
.ai-loading-bubble .dot:nth-child(1) { animation-delay: -0.32s; }
329-
.ai-loading-bubble .dot:nth-child(2) { animation-delay: -0.16s; }
330-
331-
@keyframes bounce {
332-
0%, 80%, 100% { transform: scale(0); }
333-
40% { transform: scale(1); }
334-
}
335-
`;
336-
document.head.appendChild(s);
337-
}
338389
}
339390

340391
document.addEventListener('DOMContentLoaded', initAiChat);

0 commit comments

Comments
 (0)