feat(core): 添加高级文件选择器和暗黑主题聊天界面

- 新增 AdvancedJFileChooser 类,实现记住上次访问目录的功能
- 添加 AIaToolbox_dark.html 文件,实现暗黑主题的聊天界面- 优化代码结构,提高可读性和可维护性
This commit is contained in:
tzdwindows 7
2025-05-02 19:11:25 +08:00
parent 32e4274d61
commit c4a10214db
31 changed files with 4726 additions and 78 deletions

View File

@@ -0,0 +1,867 @@
<!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>

View File

@@ -0,0 +1,665 @@
<!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: #2d8cf0;
--primary-hover: #57a3f3;
--bg-color: #f8f9fa;
--card-bg: rgba(255, 255, 255, 0.97);
--text-primary: #1f2d3d;
--text-secondary: #666;
--shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--primary-color: #58a6ff;
--primary-hover: #79b8ff;
--bg-color: #0d1117;
--card-bg: rgba(13, 17, 23, 0.95);
--text-primary: #c9d1d9;
--text-secondary: #8b949e;
--shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
}
.katex {
color: var(--text-primary);
}
.message.ai {
background: var(--card-bg);
}
pre {
background: rgba(255, 255, 255, 0.08);
}
}
.katex { font-size: 1.1em; padding: 0 0.2em; }
.math-block { margin: 1em 0; padding: 1em; background: var(--card-bg); }
.math-block::-webkit-scrollbar {
height: 6px;
}
.message.ai.response {
background: white; /* 恢复白色背景 */
color: var(--text-primary); /* 恢复正常文字颜色 */
}
.message.ai.response.collapsed .bubble {
max-height: 6em;
overflow: hidden;
position: relative;
}
.message.ai.response .fold-btn {
display: none;
background: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.95) 60%);
padding: 6px 12px;
right: 15px;
bottom: 10px;
}
.message.ai.response.collapsed .fold-btn {
display: block;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
font-family: 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif;
height: 100vh;
display: flex;
flex-direction: column;
}
.container {
max-width: 1200px;
margin: 20px auto;
flex: 1;
width: 95%;
display: flex;
flex-direction: column;
}
.chat-container {
background: var(--card-bg);
backdrop-filter: blur(12px);
border-radius: 16px;
box-shadow: var(--shadow);
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.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;
}
@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: white;
border-radius: 20px 20px 4px 20px;
}
.message.ai {
align-self: flex-start;
background: white;
color: var(--text-secondary);
border-radius: 4px 20px 20px 20px;
box-shadow: var(--shadow);
}
.bubble {
padding: 16px 24px;
line-height: 1.7;
position: relative;
transition: all 0.3s ease;
}
.streaming-cursor {
display: inline-block;
width: 8px;
height: 1em;
background: var(--text-secondary);
margin-left: 4px;
vertical-align: middle;
animation: cursorPulse 1.2s ease-in-out infinite;
}
@keyframes cursorPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.typing-indicator {
display: none;
padding: 16px 24px;
background: white;
border-radius: 24px;
margin: 12px 0;
box-shadow: var(--shadow);
align-self: flex-start;
}
.dot-flashing {
position: relative;
width: 8px;
height: 8px;
border-radius: 4px;
background-color: var(--text-secondary);
animation: dotFlashing 1s infinite linear;
}
@keyframes dotFlashing {
0% { background-color: var(--text-secondary); }
50%, 100% { background-color: rgba(94, 108, 130, 0.2); }
}
.input-area {
padding: 24px;
background: rgba(255, 255, 255, 0.9);
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.input-wrapper {
display: flex;
gap: 12px;
max-width: 800px;
margin: 0 auto;
}
input {
flex: 1;
padding: 16px 24px;
border: none;
border-radius: 30px;
background: white;
font-size: 16px;
box-shadow: var(--shadow);
transition: all 0.3s ease;
}
input:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(45, 140, 240, 0.2);
}
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;
}
button:hover {
background: var(--primary-hover);
transform: translateY(-1px);
}
pre {
background: rgba(0, 0, 0, 0.05);
padding: 16px;
border-radius: 8px;
overflow-x: auto;
position: relative;
margin: 12px 0;
}
code {
font-family: 'JetBrains Mono', monospace;
font-size: 0.9em;
}
.copy-btn {
position: absolute;
right: 12px;
top: 12px;
padding: 6px 12px;
border: none;
border-radius: 4px;
background: rgba(0, 0, 0, 0.08);
cursor: pointer;
transition: all 0.2s;
font-size: 0.85em;
}
.copy-btn:hover {
background: rgba(0, 0, 0, 0.15);
}
.fold-btn {
position: absolute;
right: 15px;
bottom: 10px;
background: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.95) 60%);
padding: 6px 12px;
border: none;
color: var(--primary-color);
font-size: 0.85em;
cursor: pointer;
display: none;
z-index: 2;
}
.message.collapsed .fold-btn,
.message:not(.streaming):hover .fold-btn {
display: block;
}
.message.collapsed .bubble {
max-height: 6em;
overflow: hidden;
}
.message.collapsed .fold-btn::after {
content: "展开";
}
.message:not(.collapsed) .fold-btn::after {
content: "收起";
}
.thinking-content {
color: #888;
font-style: italic;
margin-bottom: 12px;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.03);
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;
}
@keyframes toastIn {
from { opacity: 0; transform: translate(-50%, 10px); }
to { opacity: 1; transform: translate(-50%, 0); }
}
</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>
// 初始化配置
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>

462
javascript/HtmlViewer.html Normal file
View File

@@ -0,0 +1,462 @@
<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8">
<title>Axis Innovators Pro</title>
<!-- CodeMirror 资源 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/theme/material-darker.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/theme/nord.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/addon/hint/show-hint.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/mode/xml/xml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/addon/edit/closetag.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/addon/hint/show-hint.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.0/addon/hint/xml-hint.min.js"></script>
<style>
:root {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--border-color: #404040;
--toolbar-bg: #2d2d2d;
--primary-color: #4dabf7;
--hover-color: #339af0;
--success: #69db7c;
--warning: #ffd43b;
--error: #ff6b6b;
--radius: 6px;
}
[data-theme="light"] {
--bg-color: #f8f9fa;
--text-color: #343a40;
--border-color: #dee2e6;
--toolbar-bg: #e9ecef;
--primary-color: #4dabf7;
--hover-color: #339af0;
}
* {
box-sizing: border-box;
font-family: 'Fira Code', 'JetBrains Mono', monospace;
}
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--bg-color);
color: var(--text-color);
}
.toolbar {
padding: 12px 24px;
background-color: var(--toolbar-bg);
display: flex;
gap: 18px;
align-items: center;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.container {
flex: 1;
display: flex;
height: calc(100vh - 56px);
}
.editor-pane {
width: 50%;
position: relative;
border-right: 1px solid var(--border-color);
}
.preview-container {
width: 50%;
display: flex;
flex-direction: column;
background: var(--bg-color);
}
#preview {
flex: 1;
border: none;
background: white;
}
.output-pane {
height: 200px;
border-top: 1px solid var(--border-color);
background: var(--toolbar-bg);
overflow-y: auto;
padding: 12px;
}
.log-item {
padding: 8px 12px;
margin: 4px 0;
border-radius: var(--radius);
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
background: rgba(0,0,0,0.1);
}
.log-item.error {
color: var(--error);
border-left: 3px solid var(--error);
}
.log-item.warning {
color: var(--warning);
border-left: 3px solid var(--warning);
}
.CodeMirror {
height: 100% !important;
font-size: 14px;
line-height: 1.5;
padding: 16px 0;
}
.cm-editor {
border-radius: 0;
}
button {
background: var(--primary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: var(--radius);
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
button:hover {
background: var(--hover-color);
transform: translateY(-1px);
}
select {
padding: 8px 32px 8px 12px;
border-radius: var(--radius);
border: 1px solid var(--border-color);
background: var(--bg-color) url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3e%3cpath d='M7 10l5 5 5-5z'/%3e%3c/svg%3e") no-repeat right 8px center/12px;
color: var(--text-color);
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23999'%3e%3cpath d='M7 10l5 5 5-5z'/%3e%3c/svg%3e");
}
.file-info {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
font-size: 13px;
}
.resizer {
width: 8px;
background: var(--primary-color);
cursor: col-resize;
position: absolute;
right: -4px;
top: 0;
bottom: 0;
z-index: 10;
opacity: 0;
transition: opacity 0.2s;
}
.editor-pane:hover .resizer {
opacity: 1;
}
</style>
</head>
<body>
<div class="toolbar">
<select id="theme-selector" onchange="toggleTheme(this.value)">
<option value="dark">🌙 深色主题</option>
<option value="light">☀️ 浅色主题</option>
</select>
</div>
<div class="container">
<div class="editor-pane">
<textarea id="code-editor"></textarea>
<div class="resizer" onmousedown="startResize(event)"></div>
</div>
<div class="preview-container">
<iframe id="preview"></iframe>
<div class="output-pane" id="output"></div>
</div>
</div>
<input type="file" id="file-input" accept=".html" onchange="loadFile(this.files[0])">
<script>
// 初始化代码编辑器
const editor = CodeMirror.fromTextArea(document.getElementById('code-editor'), {
mode: "xml",
theme: "material-darker",
lineNumbers: true,
lineWrapping: true,
autoCloseTags: true,
matchTags: {bothTags: true},
extraKeys: {
"'<'": function(cm) {
cm.showHint({completeSingle: false});
},
"Ctrl-Space": "autocomplete",
"Tab": "emmetExpandAbbreviation"
},
hintOptions: {
schemaInfo: {}
},
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
});
// 实时预览
let updateTimeout;
editor.on("change", () => {
clearTimeout(updateTimeout);
updateTimeout = setTimeout(updatePreview, 300);
});
function updatePreview() {
const content = editor.getValue();
const fullHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
margin: 20px;
font-family: system-ui;
}
</style>
</head>
<body>${content}</body>
</html>
`;
const blob = new Blob([fullHTML], {type: 'text/html;charset=utf-8'});
const url = URL.createObjectURL(blob);
const iframe = document.getElementById('preview');
// 添加错误处理
iframe.onload = function() {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
const errors = [];
// 检测解析错误
const parser = new DOMParser();
const parsedDoc = parser.parseFromString(content, "text/html");
const parserErrors = parsedDoc.getElementsByTagName("parsererror");
if (parserErrors.length > 0) {
errors.push({
type: 'error',
message: 'HTML语法错误: ' + parserErrors[0].textContent
});
}
// 检测控制台错误
const consoleErrors = [];
const originalConsoleError = console.error;
console.error = function(message) {
consoleErrors.push(message);
originalConsoleError.apply(console, arguments);
};
// 触发重新解析
doc.open();
doc.write(fullHTML);
doc.close();
consoleErrors.forEach(msg => {
errors.push({type: 'error', message: msg});
});
updateOutput(errors);
URL.revokeObjectURL(url); // 释放内存
} catch(e) {
addLog('error', '预览加载失败: ' + e.message);
}
};
// 设置src最后执行
iframe.src = url;
}
// 输出控制台
function addLog(type, message) {
const output = document.getElementById('output');
const item = document.createElement('div');
item.className = `log-item ${type}`;
item.innerHTML = `
<i class="fas ${type === 'error' ? 'fa-times-circle' : 'fa-exclamation-triangle'}"></i>
${new Date().toLocaleTimeString()}: ${message}
`;
output.appendChild(item);
output.scrollTop = output.scrollHeight;
}
function updateOutput(errors) {
const output = document.getElementById('output');
output.innerHTML = '';
errors.forEach(e => addLog(e.type, e.message));
}
// 主题切换
function toggleTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
editor.setOption("theme", theme === 'dark' ? 'material-darker' : 'nord');
}
// 文件操作
function loadFile(file) {
const reader = new FileReader();
reader.onload = (e) => {
editor.setValue(e.target.result);
document.getElementById('file-name').textContent = file.name;
};
reader.readAsText(file);
}
// 窗口大小调整
let isResizing = false;
function startResize(e) {
isResizing = true;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', stopResize);
}
function onMouseMove(e) {
if (!isResizing) return;
const container = document.querySelector('.container');
const editorPane = document.querySelector('.editor-pane');
const previewPane = document.querySelector('.preview-container');
const containerWidth = container.offsetWidth;
const newWidth = (e.clientX / containerWidth) * 100;
editorPane.style.width = Math.min(Math.max(newWidth, 20), 80) + '%';
previewPane.style.width = (100 - newWidth) + '%';
}
function stopResize() {
isResizing = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', stopResize);
}
window.addEventListener('error', function(e) {
addLog('error', `运行时错误: ${e.message} (${e.lineno}:${e.colno})`);
});
// 初始化
window.addEventListener('load', () => {
// 初始化后立即更新预览
updatePreview();
// 添加编辑器变更的防抖处理
editor.on("change", () => {
clearTimeout(updateTimeout);
updateTimeout = setTimeout(() => {
updatePreview();
}, 500); // 调整为500ms防抖
});
});
(function() {
// 存储回调的映射
const callbacks = new Map();
let requestId = 0;
// 实现cefQuery规范
window.cefQuery = function(config) {
const currentId = ++requestId;
callbacks.set(currentId, {
onSuccess: config.onSuccess,
onFailure: config.onFailure
});
// 发送到Java端
window.javaMessageRouterQuery({
request: JSON.stringify({
id: currentId,
data: config.request
}),
onSuccess: (response) => {
const cb = callbacks.get(currentId);
cb?.onSuccess(response);
callbacks.delete(currentId);
},
onFailure: (error, code) => {
const cb = callbacks.get(currentId);
cb?.onFailure?.(error, code);
callbacks.delete(currentId);
}
});
};
// 初始化逻辑
document.addEventListener('DOMContentLoaded', () => {
window.cefQuery({
request: JSON.stringify({ type: "loadInitialContent" }),
onSuccess: (response) => {
try {
if (window.javaMessageHandler?.loadContent) {
javaMessageHandler.loadContent(response, '默认文档');
}
} catch(e) {
console.error('内容处理失败:', e);
}
},
onFailure: (error, code) => {
console.error(`加载失败 (CODE ${code}):`, error);
showErrorDialog(`加载失败: ${error}`);
}
});
});
// 错误显示函数
function showErrorDialog(msg) {
const dialog = document.createElement('div');
dialog.style = `/* 样式代码 */`;
dialog.innerHTML = `
<div class="error-dialog">
<i class="fa fa-exclamation-triangle"></i>
<p>${msg}</p>
<button onclick="this.parentElement.remove()">确定</button>
</div>
`;
document.body.appendChild(dialog);
}
})();
window.javaMessageHandler = {
loadContent: (content, fileName) => {
editor.setValue(content);
document.getElementById('file-name').textContent = fileName || '';
updatePreview();
},
setTheme: (theme) => {
toggleTheme(theme);
document.getElementById('theme-selector').value = theme;
}
};
</script>
</body>
</html>