Files
window-axis-innovators-box/javascript/AIaToolbox_dark.html
tzdwindows 7 c4a10214db feat(core): 添加高级文件选择器和暗黑主题聊天界面
- 新增 AdvancedJFileChooser 类,实现记住上次访问目录的功能
- 添加 AIaToolbox_dark.html 文件,实现暗黑主题的聊天界面- 优化代码结构,提高可读性和可维护性
2025-05-02 19:11:25 +08:00

867 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>DeepSeek - 智能助手</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- 动态加载主题样式 -->
<link id="hljs-light" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/intellij-light.min.css" media="(prefers-color-scheme: light)">
<link id="hljs-dark" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" media="(prefers-color-scheme: dark)">
<!-- KaTeX 核心样式 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<!-- KaTeX 核心库 -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<!-- 自动渲染扩展 -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
<style>
:root {
/* 色彩体系 */
--primary-color: #58a6ff;
--primary-hover: #79b8ff;
--bg-gradient: linear-gradient(158deg, #0a0d12 0%, #141920 100%);
--card-bg: rgba(22, 26, 34, 0.98);
--text-primary: #e3e6eb;
--text-secondary: #8b949e;
--border-color: rgba(255, 255, 255, 0.12);
--shadow: 0 12px 32px -8px rgba(0, 0, 0, 0.4);
--code-bg: rgba(255, 255, 255, 0.08);
/* 状态色 */
--success: #3ba776;
--warning: #d29922;
--error: #da3633;
/* 特殊效果 */
--glow-effect: radial-gradient(circle at 50% 0%, rgba(88,166,255,0.15) 0%, transparent 70%);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: var(--bg-gradient);
color: var(--text-primary);
font-family: 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif;
height: 100vh;
display: flex;
flex-direction: column;
line-height: 1.6;
}
/* 容器样式 */
.container {
max-width: 1200px;
margin: 20px auto;
flex: 1;
width: 95%;
display: flex;
flex-direction: column;
position: relative;
}
.chat-container {
background: transparent !important;
backdrop-filter: none;
border: none;
box-shadow: none;
}
/* 消息区域 */
.messages {
flex: 1;
padding: 24px;
overflow-y: auto;
scroll-behavior: smooth;
display: flex;
flex-direction: column;
gap: 20px;
}
/* 消息气泡 */
.message {
max-width: 85%;
opacity: 0;
animation: messageIn 0.3s ease-out forwards;
position: relative;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.message:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px -6px rgba(0, 0, 0, 0.3);
}
@keyframes messageIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 用户消息 */
.message.user {
align-self: flex-end;
background: var(--primary-color);
color: #ffffff;
border-radius: 20px 20px 4px 20px;
box-shadow: 0 4px 12px -2px rgba(88, 166, 255, 0.3);
}
/* AI消息 */
.message.ai {
align-self: flex-start;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 4px 20px 20px 20px;
box-shadow: var(--shadow);
}
.bubble {
padding: 18px 24px;
line-height: 1.8;
position: relative;
transition: all 0.3s ease;
}
/* 流式光标 */
.streaming-cursor {
display: inline-block;
width: 8px;
height: 1em;
background: var(--text-primary);
margin-left: 4px;
vertical-align: middle;
animation: cursorPulse 1.2s ease-in-out infinite;
}
@keyframes cursorPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* 输入区域 */
.input-area {
padding: 24px;
background: rgba(22, 26, 34, 0.95);
border-top: 1px solid var(--border-color);
backdrop-filter: blur(8px);
}
.input-wrapper {
display: flex;
gap: 12px;
max-width: 800px;
margin: 0 auto;
position: relative;
}
input {
flex: 1;
padding: 16px 24px;
border: 1px solid var(--border-color);
border-radius: 30px;
background: rgba(255, 255, 255, 0.05);
color: var(--text-primary);
font-size: 16px;
transition: all 0.3s ease;
}
input:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
border-color: var(--primary-color);
}
/* 按钮样式 */
button {
padding: 16px 32px;
border: none;
border-radius: 30px;
background: var(--primary-color);
color: white;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
position: relative;
overflow: hidden;
}
button:hover {
background: var(--primary-hover);
transform: translateY(-1px);
}
button::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(120deg,
rgba(255,255,255,0.1) 0%,
rgba(255,255,255,0.05) 50%,
rgba(255,255,255,0.1) 100%
);
opacity: 0;
transition: opacity 0.3s;
}
button:hover::after {
opacity: 1;
}
/* 代码块样式 */
pre {
background: var(--code-bg);
border: 1px solid var(--border-color);
padding: 16px;
border-radius: 8px;
overflow-x: auto;
position: relative;
margin: 12px 0;
}
code {
font-family: 'JetBrains Mono', monospace;
font-size: 0.9em;
color: var(--text-primary);
}
/* 复制按钮 */
.copy-btn {
position: absolute;
right: 12px;
top: 12px;
padding: 6px 12px;
border: none;
border-radius: 4px;
background: rgba(255,255,255,0.1);
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s;
font-size: 0.85em;
}
.copy-btn:hover {
background: rgba(255,255,255,0.15);
}
/* 折叠按钮 */
.fold-btn {
position: absolute;
right: 15px;
bottom: 10px;
background: linear-gradient(180deg,
rgba(22,26,34,0) 0%,
rgba(22,26,34,0.95) 60%
);
padding: 6px 12px;
border: none;
color: var(--primary-color);
font-size: 0.85em;
cursor: pointer;
display: none;
z-index: 2;
border-radius: 4px;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 8px;
background: var(--card-bg);
}
::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.2);
border-radius: 4px;
}
/* 其他组件 */
.thinking-content {
color: var(--text-secondary);
font-style: italic;
margin-bottom: 12px;
padding: 8px 12px;
background: rgba(255,255,255,0.05);
border-radius: 6px;
}
.toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.85);
color: white;
padding: 12px 24px;
border-radius: 30px;
font-size: 0.9em;
animation: toastIn 0.3s ease-out;
backdrop-filter: blur(4px);
}
/* 数学公式样式 */
.katex {
font-size: 1.1em;
padding: 0 0.2em;
color: var(--text-primary) !important;
}
.math-block {
margin: 1em 0;
padding: 1em;
background: var(--code-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/java.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
<div class="container">
<div class="chat-container">
<div class="messages" id="messages">
<div class="typing-indicator" id="typing">
<div class="dot-flashing"></div>
</div>
</div>
<div class="input-area">
<div class="input-wrapper">
<input type="text"
id="input"
placeholder="输入您的问题..."
autocomplete="off">
<button onclick="sendMessage()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
</svg>
发送
</button>
</div>
</div>
</div>
</div>
<script>
class EffectSystem {
constructor() {
this.initCanvas();
this.initParticleSystem();
this.initMouseEffects();
this.createStarryBackground();
this.lastMeteorTime = 0;
}
initCanvas() {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.canvas.style.cssText = `
position: fixed;
top: 0;
left: 0;
pointer-events: none;
z-index: 0;
`;
document.body.prepend(this.canvas);
this.resize();
window.addEventListener('resize', () => this.resize());
}
resize() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
initParticleSystem() {
this.particles = [];
this.animate();
}
animate() {
this.ctx.fillStyle = 'rgba(0,0,0,0.05)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.particles.forEach((particle, index) => {
particle.update();
particle.draw();
if (particle.alpha <= 0) this.particles.splice(index, 1);
});
requestAnimationFrame(() => this.animate());
}
initMouseEffects() {
// 点击特效
document.addEventListener('click', (e) => {
for(let i = 0; i < 15; i++) {
this.particles.push(new SparkParticle(e.clientX, e.clientY, this.ctx));
}
});
// 流星特效每3秒随机生成
setInterval(() => {
if (Math.random() > 0.7) { // 30%概率生成
this.createMeteor();
}
}, 3000);
}
createMeteor() {
const startX = Math.random() * this.canvas.width;
const meteor = new MeteorParticle(
startX,
-50,
this.ctx,
this.canvas.width,
this.canvas.height
);
this.particles.push(meteor);
}
createStarryBackground() {
// 绘制静态星空
this.ctx.fillStyle = 'rgba(255,255,255,0.8)';
for(let i = 0; i < 150; i++) {
const x = Math.random() * this.canvas.width;
const y = Math.random() * this.canvas.height;
const r = Math.random() * 1.2;
this.ctx.beginPath();
this.ctx.arc(x, y, r, 0, Math.PI * 2);
this.ctx.fill();
}
}
}
class SparkParticle {
constructor(x, y, ctx) {
this.ctx = ctx;
this.x = x;
this.y = y;
this.alpha = 1;
this.velocity = {
x: (Math.random() - 0.5) * 4,
y: (Math.random() - 0.5) * 4
};
this.size = Math.random() * 2 + 1;
this.color = `hsla(${190 + Math.random() * 40}, 70%, 60%, ${this.alpha})`;
}
update() {
this.x += this.velocity.x;
this.y += this.velocity.y;
this.alpha -= 0.02;
this.velocity.x *= 0.95;
this.velocity.y *= 0.95;
}
draw() {
this.ctx.fillStyle = this.color;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
this.ctx.fill();
}
}
class MeteorParticle {
constructor(x, y, ctx, maxWidth, maxHeight) {
this.ctx = ctx;
this.x = x;
this.y = y;
this.alpha = 1;
this.size = 2;
this.speed = 15;
this.angle = Math.PI / 4 + Math.random() * Math.PI / 2;
this.color = `hsla(180, 100%, 70%, ${this.alpha})`;
this.trail = [];
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
update() {
this.x += Math.cos(this.angle) * this.speed;
this.y += Math.sin(this.angle) * this.speed;
// 添加拖尾
this.trail.push({x: this.x, y: this.y});
if(this.trail.length > 15) this.trail.shift();
// 渐隐
this.alpha -= 0.008;
this.color = `hsla(180, 100%, 70%, ${this.alpha})`;
}
draw() {
// 绘制拖尾
this.trail.forEach((pos, i) => {
const alpha = this.alpha * (i / this.trail.length);
const gradient = this.ctx.createLinearGradient(
pos.x, pos.y,
pos.x + Math.cos(this.angle)*20,
pos.y + Math.sin(this.angle)*20
);
gradient.addColorStop(0, `hsla(180, 100%, 70%, ${alpha})`);
gradient.addColorStop(1, `hsla(180, 100%, 70%, 0)`);
this.ctx.strokeStyle = gradient;
this.ctx.lineWidth = this.size;
this.ctx.beginPath();
this.ctx.moveTo(pos.x, pos.y);
this.ctx.lineTo(
pos.x + Math.cos(this.angle)*20,
pos.y + Math.sin(this.angle)*20
);
this.ctx.stroke();
});
}
}
// 初始化特效
//if(document.documentElement.getAttribute('data-theme') === 'dark') {
new EffectSystem();
//}
// 增强毛玻璃效果
document.querySelectorAll('.chat-container, .message.ai .bubble').forEach(el => {
el.style.cssText += `
background: rgba(30, 35, 45, 0.85) !important;
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
border: 1px solid rgba(255,255,255,0.1);
`;
});
// 层级调整
document.querySelector('.container').style.cssText += `
position: relative;
z-index: 1;
`;
// 修改原有createMessageElement函数
function createMessageElement(requestId) {
const element = document.createElement('div');
element.className = 'message ai response';
element.innerHTML = `
<div class="bubble" style="
background: rgba(255,255,255,0.05);
backdrop-filter: blur(8px);
border: 1px solid rgba(255,255,255,0.1);
">
<div class="content"></div>
<div class="streaming-cursor"></div>
<button class="fold-btn" onclick="toggleFold(event)">展开</button>
</div>
`;
messages.insertBefore(element, typing);
return element;
}
// 初始化配置
marked.setOptions({
breaks: true,
highlight: code => hljs.highlightAuto(code).value
});
marked.use({
extensions: []
});
hljs.configure({
languages: ['java', 'python', 'javascript', 'typescript'],
cssSelector: 'pre code',
ignoreUnescapedHTML: true
});
// CEF通信桥接
window.javaQuery = window.cefQuery ? (request, success, error) => {
window.cefQuery({
request,
onSuccess: success,
onFailure: (code, msg) => error?.(msg)
});
} : console.error;
// 流式响应处理器
const streams = new Map();
window.updateResponse = (requestId, content) => {
if (content === '[end]') {
finalizeStream(requestId);
return;
}
let stream = streams.get(requestId);
if (!stream) {
stream = {
buffer: "", // 主缓冲区
element: createMessageElement(requestId),
cursorTimer: null,
isCompleted: false
};
streams.set(requestId, stream);
startCursorAnimation(requestId);
hideTyping();
}
// 直接累积内容到主缓冲区
stream.buffer += content;
// 实时更新到 DOM无论公式是否闭合
renderContent(requestId);
maintainScroll();
};
function hideTyping() {
document.getElementById('typing').style.display = 'none';
}
function handleThinkingContent(stream, content) {
const parts = content.split('</think>');
if (parts[0]) {
stream.hasThinking = true;
appendThinkingContent(stream, parts[0]);
}
if (parts[1]) {
stream.buffer += parts[1];
}
}
function appendThinkingContent(stream, content) {
const thinkingDiv = document.createElement('div');
thinkingDiv.className = 'thinking-content';
thinkingDiv.textContent = content.replace('<think>', '').trim();
stream.element.querySelector('.content').appendChild(thinkingDiv);
}
function startCursorAnimation(requestId) {
const stream = streams.get(requestId);
if (!stream) return;
stream.cursorTimer = setInterval(() => {
if (stream.isCompleted) {
clearInterval(stream.cursorTimer);
return;
}
const cursor = stream.element.querySelector('.streaming-cursor');
if (cursor) {
cursor.style.opacity = cursor.style.opacity === '1' ? '0.3' : '1';
}
}, 600);
}
function renderContent(requestId) {
const stream = streams.get(requestId);
if (!stream) return;
const contentDiv = stream.element.querySelector('.content');
const rawContent = stream.buffer;
// 使用 Marked 解析内容
const tempDiv = document.createElement('div');
tempDiv.innerHTML = marked.parse(rawContent);
// 高亮代码块
tempDiv.querySelectorAll('pre code').forEach(block => {
hljs.highlightElement(block);
});
// 增量更新 DOM
contentDiv.innerHTML = tempDiv.innerHTML;
// 触发 KaTeX 渲染(兼容未闭合公式)
if (window.renderMathInElement) {
renderMathInElement(contentDiv, {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '$', right: '$', display: false }
],
throwOnError: false, // 忽略错误,允许未闭合公式临时显示
strict: false // 宽松模式,兼容不完整语法
});
}
// 显示流式光标
if (!stream.isCompleted && !contentDiv.querySelector('.streaming-cursor')) {
contentDiv.innerHTML += '<div class="streaming-cursor"></div>';
}
addCopyButtons(contentDiv);
}
///function createMessageElement(requestId) {
/// const element = document.createElement('div');
/// element.className = 'message ai response'; // 添加response类
/// element.innerHTML = `
/// <div class="bubble">
/// <div class="content"></div>
/// <div class="streaming-cursor"></div>
/// <button class="fold-btn" onclick="toggleFold(event)">展开</button>
/// </div>
/// `;
/// messages.insertBefore(element, typing);
/// return element;
///}
function finalizeStream(requestId) {
const stream = streams.get(requestId);
if (stream) {
clearInterval(stream.cursorTimer);
stream.isCompleted = true;
// 移除光标
const cursor = stream.element.querySelector('.streaming-cursor');
if (cursor) cursor.remove();
// 自动折叠逻辑
checkCollapsible(stream.element);
addFoldButton(stream.element);
streams.delete(requestId);
}
}
function checkCollapsible(element) {
const bubble = element.querySelector('.bubble');
const lineHeight = parseInt(getComputedStyle(bubble).lineHeight);
if (bubble.scrollHeight > lineHeight * 5) {
element.classList.add('collapsed');
}
}
function addFoldButton(element) {
const btn = element.querySelector('.fold-btn');
btn.style.display = 'block';
btn.textContent = element.classList.contains('collapsed') ? '展开' : '收起';
}
function toggleFold(event) {
const btn = event.target;
const message = btn.closest('.message');
const beforeHeight = messages.scrollHeight;
message.classList.toggle('collapsed');
btn.textContent = message.classList.contains('collapsed') ? '展开' : '收起';
const heightDiff = messages.scrollHeight - beforeHeight;
messages.scrollTop += heightDiff;
}
function maintainScroll() {
const threshold = 100;
const container = document.getElementById('messages');
const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight <= threshold;
if (isNearBottom) {
requestAnimationFrame(() => {
container.scrollTop = container.scrollHeight;
});
}
}
function addCopyButtons(container) {
container.querySelectorAll('pre').forEach(pre => {
if (!pre.querySelector('.copy-btn')) {
const btn = document.createElement('button');
btn.className = 'copy-btn';
btn.textContent = '复制';
btn.onclick = () => copyCode(pre);
pre.prepend(btn);
}
});
}
function copyCode(pre) {
const code = pre.querySelector('code')?.textContent || '';
navigator.clipboard.writeText(code).then(() => {
showToast('代码已复制');
});
}
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 2000);
}
function sendMessage() {
const input = document.getElementById('input');
const prompt = input.value.trim();
if (!prompt) return;
const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 4)}`;
const userMsg = document.createElement('div');
userMsg.className = 'message user';
userMsg.innerHTML = `
<div class="bubble">${marked.parse(prompt)}</div>
`;
messages.insertBefore(userMsg, typing);
messages.scrollTop = messages.scrollHeight;
showTyping(true);
input.value = '';
window.javaQuery(
`ai-inference:${requestId}:${prompt}`,
response => {
if (response.startsWith("COMPLETED:")) {
finalizeStream(requestId);
}
},
error => showError(requestId, error)
);
}
function showError(requestId, message) {
const stream = streams.get(requestId);
if (stream) {
stream.element.innerHTML = `
<div class="bubble error">
<strong>⚠️ 请求失败:</strong> ${message}
</div>
`;
streams.delete(requestId);
}
showTyping(false);
}
function showTyping(show) {
typing.style.display = show ? 'block' : 'none';
if (show) messages.scrollTop = messages.scrollHeight;
}
document.getElementById('input').addEventListener('keypress', e => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
document.addEventListener('DOMContentLoaded', () => {
renderMathInElement(document.body, {
delimiters: [
{ left: '$$', right: '$$', display: true }, // 块级公式
{ left: '$', right: '$', display: false }, // 行内公式
{ left: '\\[', right: '\\]', display: true }, // LaTeX 环境
{ left: '\\(', right: '\\)', display: false }
],
throwOnError: false // 忽略渲染错误
});
});
</script>
</body>
</html>