feat(core): 添加高级文件选择器和暗黑主题聊天界面
- 新增 AdvancedJFileChooser 类,实现记住上次访问目录的功能 - 添加 AIaToolbox_dark.html 文件,实现暗黑主题的聊天界面- 优化代码结构,提高可读性和可维护性
10
build.gradle
@@ -145,6 +145,16 @@ dependencies {
|
||||
implementation 'jflac:jflac:1.3' // FLAC支持
|
||||
|
||||
implementation 'com.github.axet:TarsosDSP:2.4'
|
||||
|
||||
// Eclipse 组件
|
||||
// https://mvnrepository.com/artifact/org.eclipse.swt/org.eclipse.swt.win32.win32.x86_64
|
||||
//implementation 'org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:4.3'
|
||||
//
|
||||
//// 基础 Eclipse 组件
|
||||
//implementation 'org.eclipse.platform:org.eclipse.jface:3.36.0'
|
||||
//implementation 'org.eclipse.platform:org.eclipse.jface.text:3.27.0' // 使用兼容版本
|
||||
//implementation 'org.eclipse.platform:org.eclipse.core.runtime:3.33.0'
|
||||
//implementation 'org.eclipse.platform:org.eclipse.equinox.common:3.20.0'
|
||||
}
|
||||
|
||||
// 分离依赖项到 libs 目录
|
||||
|
||||
867
javascript/AIaToolbox_dark.html
Normal 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>
|
||||
665
javascript/AIaToolbox_light.html
Normal 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
@@ -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>
|
||||
@@ -1,3 +1,3 @@
|
||||
#Current Loaded Language
|
||||
#Sun Mar 02 19:35:49 CST 2025
|
||||
#Thu May 01 15:04:30 CST 2025
|
||||
loadedLanguage=system\:zh_CN
|
||||
|
||||
@@ -319,6 +319,10 @@ public class AxisInnovatorsBox {
|
||||
windowsJDialog.repaint();
|
||||
}
|
||||
|
||||
//public void execute(Runnable runnable){
|
||||
// thread.
|
||||
//}
|
||||
|
||||
/**
|
||||
* 重新加载窗口
|
||||
*/
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package com.axis.innovators.box;
|
||||
|
||||
import com.axis.innovators.box.browser.MainApplication;
|
||||
import com.axis.innovators.box.decompilation.gui.ModernJarViewer;
|
||||
import com.axis.innovators.box.events.GlobalEventBus;
|
||||
import com.axis.innovators.box.events.OpenFileEvents;
|
||||
import com.axis.innovators.box.gui.LoginWindow;
|
||||
import com.axis.innovators.box.tools.ArgsParser;
|
||||
import com.axis.innovators.box.tools.FolderCleaner;
|
||||
import com.axis.innovators.box.tools.FolderCreator;
|
||||
import com.axis.innovators.box.register.LanguageManager;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -42,6 +39,11 @@ public class Main {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (".html".equals(extension)){
|
||||
MainApplication.popupHTMLWindow(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AxisInnovatorsBox.run(args);
|
||||
|
||||
@@ -56,6 +56,7 @@ public class CefAppManager {
|
||||
|
||||
private static void disposeCefApp() {
|
||||
if (cefApp != null) {
|
||||
cefApp.clearSchemeHandlerFactories();
|
||||
cefApp.dispose();
|
||||
cefApp = null;
|
||||
isInitialized = false;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.axis.innovators.box.browser;
|
||||
|
||||
import com.axis.innovators.box.tools.FolderCreator;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.cef.browser.CefBrowser;
|
||||
import org.cef.browser.CefFrame;
|
||||
import org.cef.browser.CefMessageRouter;
|
||||
@@ -8,9 +11,13 @@ import org.cef.handler.CefMessageRouterHandlerAdapter;
|
||||
import org.tzd.lm.LM;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -27,46 +34,7 @@ public class MainApplication {
|
||||
private static long ctxHandle;
|
||||
private static boolean isSystem = true;
|
||||
public static void main(String[] args) {
|
||||
LM.loadLibrary(LM.CUDA);
|
||||
modelHandle = LM.llamaLoadModelFromFile(LM.DEEP_SEEK);
|
||||
ctxHandle = LM.createContext(modelHandle);
|
||||
|
||||
AtomicReference<BrowserWindowJDialog> window = new AtomicReference<>();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
WindowRegistry.getInstance().createNewChildWindow("main", builder ->
|
||||
window.set(builder.title("Axis Innovators Box AI 工具箱")
|
||||
.icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage())
|
||||
.size(1280, 720)
|
||||
.htmlPath(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("javascript/AIaToolbox.html")).getFile())
|
||||
.operationHandler(createOperationHandler())
|
||||
.build())
|
||||
);
|
||||
|
||||
CefMessageRouter msgRouter = window.get().getMsgRouter();
|
||||
if (msgRouter != null) {
|
||||
msgRouter.addHandler(new CefMessageRouterHandlerAdapter() {
|
||||
@Override
|
||||
public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId,
|
||||
String request, boolean persistent, CefQueryCallback callback) {
|
||||
// 处理浏览器请求
|
||||
handleBrowserQuery(browser, request, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) {
|
||||
// 处理请求取消
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭钩子
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
LM.llamaFreeContext(ctxHandle);
|
||||
LM.llamaFreeModel(modelHandle);
|
||||
executor.shutdown();
|
||||
}));
|
||||
//popupHTMLWindow();
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +55,7 @@ public class MainApplication {
|
||||
.parentFrame(parent)
|
||||
.icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage())
|
||||
.size(1280, 720)
|
||||
.htmlPath(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("javascript/AIaToolbox_dark.html")).getFile())
|
||||
.htmlPath(FolderCreator.getJavaScriptFolder() + "\\" + "AIaToolbox_dark.html")
|
||||
.operationHandler(createOperationHandler())
|
||||
.build())
|
||||
);
|
||||
@@ -110,13 +78,60 @@ public class MainApplication {
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭钩子
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
LM.llamaFreeContext(ctxHandle);
|
||||
LM.llamaFreeModel(modelHandle);
|
||||
executor.shutdown();
|
||||
}));
|
||||
/**
|
||||
* 弹出html预览窗口
|
||||
*/
|
||||
public static void popupHTMLWindow(String path) {
|
||||
AtomicReference<BrowserWindow> window = new AtomicReference<>();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
WindowRegistry.getInstance().createNewWindow("main", builder ->
|
||||
window.set(builder.title("Axis Innovators Box HTML查看器")
|
||||
.icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage())
|
||||
.size(1487, 836)
|
||||
.htmlPath(FolderCreator.getJavaScriptFolder() + "\\" + "HtmlViewer.html")
|
||||
.operationHandler(createOperationHandler())
|
||||
.build())
|
||||
);
|
||||
|
||||
CefMessageRouter msgRouter = window.get().getMsgRouter();
|
||||
if (msgRouter != null) {
|
||||
msgRouter.addHandler(new CefMessageRouterHandlerAdapter() {
|
||||
@Override
|
||||
public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId,
|
||||
String request, boolean persistent, CefQueryCallback callback) {
|
||||
try {
|
||||
// 解析JSON请求
|
||||
JsonObject requestJson = JsonParser.parseString(request).getAsJsonObject();
|
||||
if (requestJson.has("type") && "loadInitialContent".equals(requestJson.get("type").getAsString())) {
|
||||
|
||||
Path filePath = Paths.get(path);
|
||||
|
||||
// 验证文件存在性
|
||||
if (!Files.exists(filePath)) {
|
||||
callback.failure(404, "{\"code\":404,\"message\":\"文件未找到\"}");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
String content = Files.readString(filePath, StandardCharsets.UTF_8);
|
||||
callback.success(content);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
callback.failure(500, "{\"code\":500,\"message\":\"" + e.getMessage() + "\"}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) {
|
||||
// 处理请求取消
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void handleBrowserQuery(CefBrowser browser, String request, CefQueryCallback callback) {
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
package com.axis.innovators.box.decompilation.gui;
|
||||
|
||||
import org.objectweb.asm.*;
|
||||
import org.objectweb.asm.tree.*;
|
||||
import org.objectweb.asm.tree.analysis.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
public class JavaPseudocodeGenerator extends MethodVisitor {
|
||||
private final StringBuilder output = new StringBuilder();
|
||||
private int indentLevel = 0;
|
||||
private final Deque<ControlStructure> controlStack = new ArrayDeque<>();
|
||||
private final Map<Label, Integer> labelPositions = new HashMap<>();
|
||||
private int currentPosition = 0;
|
||||
|
||||
// 类型推断相关数据结构
|
||||
private final Map<Integer, Type> localVarTypes = new HashMap<>();
|
||||
private final Analyzer<BasicValue> analyzer;
|
||||
private Frame<BasicValue>[] frames;
|
||||
private final Deque<TypeValue> operandStack = new ArrayDeque<>();
|
||||
private final MethodNode methodNode;
|
||||
|
||||
private static class TypeValue {
|
||||
final Type type;
|
||||
final String expression;
|
||||
|
||||
TypeValue(Type type, String expression) {
|
||||
this.type = type;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ControlStructure {
|
||||
enum Type { LOOP, CONDITIONAL, TRY_CATCH }
|
||||
|
||||
private final String header;
|
||||
private final Type type;
|
||||
private Label endLabel;
|
||||
|
||||
ControlStructure(String header, Type type) {
|
||||
this.header = header;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
ControlStructure setEndLabel(Label endLabel) {
|
||||
this.endLabel = endLabel;
|
||||
return this;
|
||||
}
|
||||
|
||||
String getHeader() { return header; }
|
||||
Label getEndLabel() { return endLabel; }
|
||||
}
|
||||
|
||||
public JavaPseudocodeGenerator(MethodNode methodNode) throws AnalyzerException {
|
||||
super(Opcodes.ASM9);
|
||||
this.methodNode = methodNode;
|
||||
this.analyzer = new Analyzer<>(new BasicInterpreter());
|
||||
this.frames = analyzer.analyze(methodNode.name, methodNode);
|
||||
initLabelPositions(methodNode);
|
||||
analyzeLocalVariables(methodNode);
|
||||
}
|
||||
|
||||
private void initLabelPositions(MethodNode methodNode) {
|
||||
labelPositions.clear();
|
||||
int pos = 0;
|
||||
for (AbstractInsnNode insn : methodNode.instructions) {
|
||||
if (insn instanceof LabelNode) {
|
||||
Label label = ((LabelNode) insn).getLabel();
|
||||
labelPositions.put(label, pos);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeLocalVariables(MethodNode methodNode) {
|
||||
localVarTypes.clear();
|
||||
if (methodNode.localVariables != null) {
|
||||
for (LocalVariableNode local : methodNode.localVariables) {
|
||||
Type type = Type.getType(local.desc);
|
||||
localVarTypes.put(local.index, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getOutput() {
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCode() {
|
||||
// 生成方法签名
|
||||
String access = getAccessModifier(methodNode.access);
|
||||
Type returnType = Type.getReturnType(methodNode.desc);
|
||||
Type[] argTypes = Type.getArgumentTypes(methodNode.desc);
|
||||
|
||||
StringBuilder signature = new StringBuilder();
|
||||
signature.append(access)
|
||||
.append(" ")
|
||||
.append(returnType.getClassName())
|
||||
.append(" ")
|
||||
.append(methodNode.name)
|
||||
.append("(");
|
||||
|
||||
List<String> params = new ArrayList<>();
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
params.add(argTypes[i].getClassName() + " arg" + i);
|
||||
}
|
||||
signature.append(String.join(", ", params))
|
||||
.append(") {");
|
||||
|
||||
addLine(signature.toString());
|
||||
indentLevel++;
|
||||
}
|
||||
|
||||
private String getAccessModifier(int access) {
|
||||
List<String> modifiers = new ArrayList<>();
|
||||
if ((access & Opcodes.ACC_PUBLIC) != 0) modifiers.add("public");
|
||||
if ((access & Opcodes.ACC_PRIVATE) != 0) modifiers.add("private");
|
||||
if ((access & Opcodes.ACC_PROTECTED) != 0) modifiers.add("protected");
|
||||
if ((access & Opcodes.ACC_STATIC) != 0) modifiers.add("static");
|
||||
return String.join(" ", modifiers);
|
||||
}
|
||||
|
||||
private void pushValue(Type type, String expr) {
|
||||
operandStack.push(new TypeValue(type, expr));
|
||||
}
|
||||
|
||||
private TypeValue popValue(Type expectedType) {
|
||||
if (operandStack.isEmpty()) {
|
||||
return new TypeValue(expectedType, "/* missing value */");
|
||||
}
|
||||
TypeValue value = operandStack.pop();
|
||||
if (!value.type.equals(expectedType)) {
|
||||
return new TypeValue(expectedType, "/* type error: " + value.expression + " */");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
switch (opcode) {
|
||||
case Opcodes.RETURN:
|
||||
addLine("return;");
|
||||
break;
|
||||
case Opcodes.IRETURN:
|
||||
addLine("return " + popValue(Type.INT_TYPE) + ";");
|
||||
break;
|
||||
case Opcodes.ARETURN:
|
||||
addLine("return " + popValue(Type.getType(Object.class)) + ";");
|
||||
break;
|
||||
case Opcodes.ATHROW:
|
||||
addLine("throw " + popValue(Type.getType(Throwable.class)) + ";");
|
||||
break;
|
||||
case Opcodes.IADD: {
|
||||
TypeValue val2 = popValue(Type.INT_TYPE);
|
||||
TypeValue val1 = popValue(Type.INT_TYPE);
|
||||
pushValue(Type.INT_TYPE, val1.expression + " + " + val2.expression);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
addLine("// asm: " + OpcodesUtil.getOpcodeName(opcode));
|
||||
}
|
||||
currentPosition++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitVarInsn(int opcode, int var) {
|
||||
Type type = localVarTypes.getOrDefault(var, Type.VOID_TYPE);
|
||||
switch (opcode) {
|
||||
case Opcodes.ILOAD:
|
||||
pushValue(Type.INT_TYPE, "var" + var);
|
||||
break;
|
||||
case Opcodes.ALOAD:
|
||||
pushValue(type, "var" + var);
|
||||
break;
|
||||
case Opcodes.ISTORE:
|
||||
addLine("var" + var + " = " + popValue(Type.INT_TYPE) + ";");
|
||||
break;
|
||||
case Opcodes.ASTORE:
|
||||
addLine("var" + var + " = " + popValue(type) + ";");
|
||||
break;
|
||||
default:
|
||||
addLine("// asm: " + OpcodesUtil.getOpcodeName(opcode));
|
||||
}
|
||||
currentPosition++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, String owner, String name,
|
||||
String descriptor, boolean isInterface) {
|
||||
try {
|
||||
Type returnType = Type.getReturnType(descriptor);
|
||||
Type[] argTypes = Type.getArgumentTypes(descriptor);
|
||||
|
||||
List<String> args = new ArrayList<>();
|
||||
for (int i = argTypes.length - 1; i >= 0; i--) {
|
||||
args.add(0, popValue(argTypes[i]).toString());
|
||||
}
|
||||
|
||||
String methodCall;
|
||||
if (opcode == Opcodes.INVOKESTATIC) {
|
||||
methodCall = owner.replace('/', '.') + "." + name;
|
||||
} else {
|
||||
TypeValue target = popValue(Type.getType("L" + owner + ";"));
|
||||
methodCall = target + "." + name;
|
||||
}
|
||||
|
||||
String callExpr = methodCall + "(" + String.join(", ", args) + ")";
|
||||
if (returnType != Type.VOID_TYPE) {
|
||||
pushValue(returnType, callExpr);
|
||||
} else {
|
||||
addLine(callExpr + ";");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
addLine("// asm: " + OpcodesUtil.getOpcodeName(opcode));
|
||||
}
|
||||
currentPosition++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
try {
|
||||
TypeValue condition = popValue(Type.BOOLEAN_TYPE);
|
||||
String operator = getConditionOperator(opcode);
|
||||
|
||||
if (isLoopStart(label)) {
|
||||
addLine("while (" + condition.expression + operator + ") {");
|
||||
indentLevel++;
|
||||
controlStack.push(new ControlStructure("loop", ControlStructure.Type.LOOP).setEndLabel(label));
|
||||
} else {
|
||||
addLine("if (" + condition.expression + operator + ") {");
|
||||
indentLevel++;
|
||||
controlStack.push(new ControlStructure("if", ControlStructure.Type.CONDITIONAL).setEndLabel(label));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
addLine("// asm: " + OpcodesUtil.getOpcodeName(opcode));
|
||||
}
|
||||
currentPosition++;
|
||||
}
|
||||
|
||||
private String getConditionOperator(int opcode) {
|
||||
switch (opcode) {
|
||||
case Opcodes.IFEQ: return " == 0";
|
||||
case Opcodes.IFNE: return " != 0";
|
||||
case Opcodes.IFLT: return " < 0";
|
||||
case Opcodes.IFGE: return " >= 0";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLoopStart(Label label) {
|
||||
int targetPos = getLabelPosition(label);
|
||||
return targetPos != -1 && targetPos < currentPosition;
|
||||
}
|
||||
|
||||
private int getLabelPosition(Label label) {
|
||||
return labelPositions.getOrDefault(label, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLabel(Label label) {
|
||||
if (!controlStack.isEmpty()) {
|
||||
ControlStructure structure = controlStack.peek();
|
||||
if (structure.getEndLabel() == null) {
|
||||
structure.setEndLabel(label);
|
||||
} else if (structure.getEndLabel().equals(label)) {
|
||||
controlStack.pop();
|
||||
indentLevel--;
|
||||
addLine("}");
|
||||
}
|
||||
}
|
||||
currentPosition++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||
addLine("try {");
|
||||
indentLevel++;
|
||||
controlStack.push(new ControlStructure("try", ControlStructure.Type.TRY_CATCH).setEndLabel(end));
|
||||
currentPosition++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
while (!controlStack.isEmpty()) {
|
||||
indentLevel--;
|
||||
addLine("}");
|
||||
controlStack.pop();
|
||||
}
|
||||
indentLevel--;
|
||||
addLine("}");
|
||||
}
|
||||
|
||||
private void addLine(String line) {
|
||||
output.append(" ".repeat(indentLevel)).append(line).append("\n");
|
||||
}
|
||||
|
||||
private static class OpcodesUtil {
|
||||
private static final Map<Integer, String> OPCODE_NAMES = new HashMap<>();
|
||||
|
||||
static {
|
||||
try {
|
||||
for (Field field : Opcodes.class.getDeclaredFields()) {
|
||||
if (field.getType() == int.class) {
|
||||
OPCODE_NAMES.put(field.getInt(null), field.getName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static String getOpcodeName(int opcode) {
|
||||
return OPCODE_NAMES.getOrDefault(opcode, "UNKNOWN_OPCODE_" + opcode);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
MethodNode methodNode = new MethodNode(Opcodes.ASM9,
|
||||
Opcodes.ACC_PUBLIC, "add",
|
||||
"(II)I",
|
||||
null,
|
||||
null);
|
||||
methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 0));
|
||||
methodNode.instructions.add(new VarInsnNode(Opcodes.ILOAD, 1));
|
||||
methodNode.instructions.add(new InsnNode(Opcodes.IADD));
|
||||
methodNode.instructions.add(new InsnNode(Opcodes.IRETURN));
|
||||
|
||||
JavaPseudocodeGenerator generator = new JavaPseudocodeGenerator(methodNode);
|
||||
methodNode.accept(generator);
|
||||
System.out.println(generator.getOutput());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.axis.innovators.box.decompilation.gui;
|
||||
|
||||
import com.axis.innovators.box.gui.LoadIcon;
|
||||
import com.axis.innovators.box.util.AdvancedJFileChooser;
|
||||
import com.github.javaparser.JavaParser;
|
||||
import com.github.javaparser.ParseResult;
|
||||
import com.github.javaparser.Position;
|
||||
@@ -794,7 +795,7 @@ public class ModernJarViewer extends JFrame {
|
||||
|
||||
/****/
|
||||
private void openJarFile() {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
AdvancedJFileChooser chooser = new AdvancedJFileChooser();
|
||||
chooser.setFileFilter(new javax.swing.filechooser.FileFilter() {
|
||||
public boolean accept(File f) {
|
||||
return f.getName().endsWith(".jar") || f.isDirectory();
|
||||
@@ -829,6 +830,7 @@ public class ModernJarViewer extends JFrame {
|
||||
String name = entry.getName();
|
||||
// 忽略已被源码覆盖的class文件
|
||||
if (name.endsWith(".class") && sourceFiles.contains(name)) continue;
|
||||
|
||||
addEntryToTree(root, name);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.axis.innovators.box.decompilation.gui;
|
||||
|
||||
public class TypeMismatchException extends Exception{
|
||||
public TypeMismatchException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.axis.innovators.box.gui;
|
||||
|
||||
import com.axis.innovators.box.gui.renderer.DarculaCompletionCellRenderer;
|
||||
import com.axis.innovators.box.util.AdvancedJFileChooser;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.fife.rsta.ac.LanguageSupport;
|
||||
@@ -479,7 +480,7 @@ public class FridaWindow extends WindowsJDialog {
|
||||
|
||||
|
||||
private void openFile() {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
AdvancedJFileChooser fileChooser = new AdvancedJFileChooser();
|
||||
fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("Frida脚本配置 (*.frida)", "frida"));
|
||||
int result = fileChooser.showOpenDialog(this);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
@@ -503,7 +504,7 @@ public class FridaWindow extends WindowsJDialog {
|
||||
}
|
||||
|
||||
private void saveFile() {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
AdvancedJFileChooser fileChooser = new AdvancedJFileChooser();
|
||||
fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("Frida脚本配置 (*.frida)", "frida"));
|
||||
int result = fileChooser.showSaveDialog(this);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
|
||||
1469
src/main/java/com/axis/innovators/box/gui/JarApiProfilingWindow.java
Normal file
@@ -39,7 +39,7 @@ public class MainWindow extends JFrame {
|
||||
private final boolean isBackground = true;
|
||||
private final boolean isBlur = true;
|
||||
private SystemTray systemTray;
|
||||
private TrayIcon trayIcon;
|
||||
//private TrayIcon trayIcon;
|
||||
|
||||
public MainWindow() {
|
||||
setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage());
|
||||
@@ -123,17 +123,17 @@ public class MainWindow extends JFrame {
|
||||
popup.add(exitItem);
|
||||
|
||||
// 5. 创建托盘图标
|
||||
trayIcon = new TrayIcon(roundedImage, "轴创工具箱", popup);
|
||||
trayIcon.setImageAutoSize(true);
|
||||
//trayIcon = new TrayIcon(roundedImage, "轴创工具箱", popup);
|
||||
//trayIcon.setImageAutoSize(true);
|
||||
|
||||
// 6. 添加事件监听
|
||||
addTrayEventListeners();
|
||||
//addTrayEventListeners();
|
||||
|
||||
try {
|
||||
systemTray.add(trayIcon);
|
||||
} catch (AWTException ex) {
|
||||
logger.error("添加系统托盘图标失败", ex);
|
||||
}
|
||||
//try {
|
||||
// systemTray.add(trayIcon);
|
||||
//} catch (AWTException ex) {
|
||||
// logger.error("添加系统托盘图标失败", ex);
|
||||
//}
|
||||
}
|
||||
|
||||
// 基础菜单项创建方法(解决方法不存在问题)
|
||||
@@ -161,20 +161,20 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
// 添加事件监听
|
||||
private void addTrayEventListeners() {
|
||||
// 双击恢复窗口
|
||||
trayIcon.addActionListener(e -> setVisible(true));
|
||||
|
||||
// 右键菜单触发兼容处理
|
||||
trayIcon.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger()) {
|
||||
trayIcon.getPopupMenu().show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
//private void addTrayEventListeners() {
|
||||
// // 双击恢复窗口
|
||||
// trayIcon.addActionListener(e -> setVisible(true));
|
||||
//
|
||||
// // 右键菜单触发兼容处理
|
||||
// trayIcon.addMouseListener(new MouseAdapter() {
|
||||
// @Override
|
||||
// public void mouseReleased(MouseEvent e) {
|
||||
// if (e.isPopupTrigger()) {
|
||||
// trayIcon.getPopupMenu().show(e.getComponent(), e.getX(), e.getY());
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
|
||||
// 初始化中文字体(在main方法或构造函数中调用)
|
||||
private void initChineseFont() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.axis.innovators.box.register;
|
||||
import com.axis.innovators.box.AxisInnovatorsBox;
|
||||
import com.axis.innovators.box.browser.MainApplication;
|
||||
import com.axis.innovators.box.gui.FridaWindow;
|
||||
import com.axis.innovators.box.gui.JarApiProfilingWindow;
|
||||
import com.axis.innovators.box.gui.LocalWindow;
|
||||
import com.axis.innovators.box.gui.MainWindow;
|
||||
import com.axis.innovators.box.plugins.PluginDescriptor;
|
||||
@@ -47,6 +48,20 @@ public class RegistrationTool {
|
||||
}
|
||||
}));
|
||||
|
||||
MainWindow.ToolCategory programmingToolsCategory = new MainWindow.ToolCategory("编程工具",
|
||||
"programming/programming.png",
|
||||
"编程工具");
|
||||
programmingToolsCategory.addTool(new MainWindow.ToolItem("JarApi查看器", "programming/JarApiViewer/JarApi_Viewer.png",
|
||||
"查看Jar内的方法以及其注解" +
|
||||
"\n作者:tzdwindows 7", ++id, new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Window owner = SwingUtilities.windowForComponent((Component) e.getSource());
|
||||
JarApiProfilingWindow jarApiProfilingWindow = new JarApiProfilingWindow(owner);
|
||||
main.popupWindow(jarApiProfilingWindow);
|
||||
}
|
||||
}));
|
||||
|
||||
MainWindow.ToolCategory aICategory = new MainWindow.ToolCategory("AI工具",
|
||||
"ai/ai.png",
|
||||
"人工智能/大语言模型");
|
||||
@@ -75,6 +90,7 @@ public class RegistrationTool {
|
||||
|
||||
addToolCategory(debugCategory, "system:debugTools");
|
||||
addToolCategory(aICategory,"system:fridaTools");
|
||||
addToolCategory(programmingToolsCategory, "system:programmingTools");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ public class FolderCreator {
|
||||
public static final String LOGS_PATH = "logs";
|
||||
public static final String LANGUAGE_PATH = "language";
|
||||
public static final String CONFIGURATION_PATH = "state";
|
||||
public static final String JAVA_SCRIPT_PATH = "javascript";
|
||||
public static String getConfigurationFolder() {
|
||||
String folder = createFolder(CONFIGURATION_PATH);
|
||||
if (folder == null) {
|
||||
@@ -25,6 +26,15 @@ public class FolderCreator {
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
public static String getJavaScriptFolder() {
|
||||
String folder = createFolder(JAVA_SCRIPT_PATH);
|
||||
if (folder == null) {
|
||||
logger.error("Plugin folder creation failed, please use administrator privileges to execute this procedure");
|
||||
return null;
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
public static String getLanguageFolder() {
|
||||
String folder = createFolder(LANGUAGE_PATH);
|
||||
if (folder == null) {
|
||||
|
||||
233
src/main/java/com/axis/innovators/box/tools/RegisterTray.java
Normal file
@@ -0,0 +1,233 @@
|
||||
package com.axis.innovators.box.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Windows系统托盘管理器
|
||||
* <p>提供系统托盘图标的管理功能,支持菜单操作和事件回调</p>
|
||||
*
|
||||
* @author tzdwindows
|
||||
* @version 1.0
|
||||
*/
|
||||
public class RegisterTray {
|
||||
|
||||
static {
|
||||
LibraryLoad.loadLibrary("RegisterTray");
|
||||
}
|
||||
|
||||
/**
|
||||
* 托盘菜单项构建器(流畅接口)
|
||||
*/
|
||||
public static class MenuBuilder {
|
||||
private final List<Item> items = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 添加菜单项
|
||||
* @param id 菜单项唯一标识符(需大于0)
|
||||
* @param label 菜单显示文本
|
||||
* @param onClick 点击事件处理器
|
||||
* @return 当前构建器实例
|
||||
*/
|
||||
public MenuBuilder addItem(int id, String label, MenuItemClickListener onClick) {
|
||||
items.add(new Item(
|
||||
id,
|
||||
label,
|
||||
"", "", "",
|
||||
(Event) combinedId -> {
|
||||
int itemId = (int)(combinedId >> 32);
|
||||
onClick.onClick(itemId);
|
||||
}
|
||||
));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建菜单项列表
|
||||
* @return 可用于注册的菜单项集合
|
||||
*/
|
||||
public List<Item> build() {
|
||||
return new ArrayList<>(items);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 托盘配置器(流畅接口)
|
||||
*/
|
||||
public static class TrayConfig {
|
||||
private String title = "Application";
|
||||
private String iconPath = "";
|
||||
private String tooltip = "";
|
||||
private List<Item> menuItems = new ArrayList<>();
|
||||
private TrayClickListener clickListener = id -> {};
|
||||
|
||||
/**
|
||||
* 设置托盘标题(必选)
|
||||
* @param title 显示在托盘区域的标题
|
||||
* @return 当前配置实例
|
||||
*/
|
||||
public TrayConfig title(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置托盘图标(可选)
|
||||
* @param iconPath ICO图标的绝对路径
|
||||
* @return 当前配置实例
|
||||
*/
|
||||
public TrayConfig icon(String iconPath) {
|
||||
this.iconPath = iconPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置悬停提示信息(可选)
|
||||
* @param tooltip 鼠标悬停时显示的文本
|
||||
* @return 当前配置实例
|
||||
*/
|
||||
public TrayConfig tooltip(String tooltip) {
|
||||
this.tooltip = tooltip;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置右键菜单(可选)
|
||||
* @param menuItems 通过MenuBuilder构建的菜单项列表
|
||||
* @return 当前配置实例
|
||||
*/
|
||||
public TrayConfig menu(List<Item> menuItems) {
|
||||
this.menuItems = new ArrayList<>(menuItems);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置托盘点击事件(可选)
|
||||
* @param listener 点击事件监听器
|
||||
* @return 当前配置实例
|
||||
*/
|
||||
public TrayConfig onClick(TrayClickListener listener) {
|
||||
this.clickListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成配置并注册托盘
|
||||
* @return 注册成功的托盘ID
|
||||
* @throws TrayException 注册失败时抛出
|
||||
*/
|
||||
public long register() throws TrayException {
|
||||
try {
|
||||
return RegisterTray.register(
|
||||
title,
|
||||
menuItems,
|
||||
iconPath,
|
||||
tooltip,
|
||||
clickListener::onClick
|
||||
);
|
||||
} catch (Exception e) {
|
||||
throw new TrayException("托盘注册失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 托盘点击事件监听器
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TrayClickListener {
|
||||
/**
|
||||
* 当托盘图标被点击时触发
|
||||
* @param trayId 托盘实例的唯一标识符
|
||||
*/
|
||||
void onClick(long trayId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单项点击事件监听器
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MenuItemClickListener {
|
||||
/**
|
||||
* 当菜单项被点击时触发
|
||||
* @param itemId 菜单项的唯一标识符
|
||||
*/
|
||||
void onClick(int itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 托盘操作异常
|
||||
*/
|
||||
public static class TrayException extends Exception {
|
||||
public TrayException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
public static native long register(String name, List<Item> value,
|
||||
String icon, String description, Event event);
|
||||
public static native void unregister(long id);
|
||||
|
||||
public interface Event {
|
||||
void onClick(long id);
|
||||
}
|
||||
|
||||
public static class Item {
|
||||
private final long id;
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final String icon;
|
||||
private final String description;
|
||||
private final Event event;
|
||||
|
||||
/**
|
||||
* @param id 菜单项唯一ID
|
||||
* @param name 显示名称
|
||||
* @param value 保留字段
|
||||
* @param icon 保留字段
|
||||
* @param description 保留字段
|
||||
* @param event 点击事件处理器
|
||||
*/
|
||||
public Item(int id, String name, String value,
|
||||
String icon, String description, Event event) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.icon = icon;
|
||||
this.description = description;
|
||||
this.event = event;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 构建菜单
|
||||
List<Item> menuItems = new MenuBuilder()
|
||||
.addItem(1, "显示日志", itemId ->
|
||||
System.out.println("打开日志,菜单项ID: " + itemId))
|
||||
.addItem(2, "退出系统", itemId -> System.exit(0))
|
||||
.build();
|
||||
|
||||
// 配置并注册托盘
|
||||
long trayId = new TrayConfig()
|
||||
.title("数据监控系统")
|
||||
.icon("C:/icons/app.ico")
|
||||
.tooltip("双击查看实时数据")
|
||||
.menu(menuItems)
|
||||
.onClick(id ->
|
||||
System.out.println("托盘被点击,ID: " + id))
|
||||
.register();
|
||||
|
||||
// 程序主循环
|
||||
while (true) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
} catch (RegisterTray.TrayException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
//static {
|
||||
// System.loadLibrary("RegisterTray");
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.axis.innovators.box.util;
|
||||
|
||||
import com.axis.innovators.box.gui.JarApiProfilingWindow;
|
||||
import com.formdev.flatlaf.FlatLaf;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* @author tzdwindows 7
|
||||
*/
|
||||
public class AdvancedJFileChooser extends JFileChooser {
|
||||
private static final String PREF_KEY = "LAST_DIRECTORY";
|
||||
private static final File FALLBACK_DIR = new File(System.getProperty("user.home"));
|
||||
|
||||
public AdvancedJFileChooser() {
|
||||
super(loadValidDirectory());
|
||||
setFileSelectionMode(FILES_AND_DIRECTORIES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int showDialog(Component parent, String approveButtonText) {
|
||||
int result = super.showDialog(parent, approveButtonText);
|
||||
if (result == APPROVE_OPTION) {
|
||||
persistDirectory(getSelectedPath());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getSelectedPath() {
|
||||
File selected = getSelectedFile();
|
||||
return (selected != null && selected.isDirectory()) ?
|
||||
selected.getAbsolutePath() :
|
||||
getCurrentDirectory().getAbsolutePath();
|
||||
}
|
||||
|
||||
private static File loadValidDirectory() {
|
||||
try {
|
||||
Preferences prefs = Preferences.userNodeForPackage(JarApiProfilingWindow.class);
|
||||
String path = prefs.get(PREF_KEY, FALLBACK_DIR.getAbsolutePath());
|
||||
|
||||
File dir = new File(path).getCanonicalFile();
|
||||
return dir.isDirectory() && Files.isReadable(dir.toPath()) ?
|
||||
dir :
|
||||
FALLBACK_DIR;
|
||||
|
||||
} catch (IOException | InvalidPathException | SecurityException e) {
|
||||
return FALLBACK_DIR;
|
||||
}
|
||||
}
|
||||
|
||||
private void persistDirectory(String path) {
|
||||
try {
|
||||
File dir = new File(path).getCanonicalFile();
|
||||
if (dir.isDirectory() && Files.isWritable(dir.toPath())) {
|
||||
Preferences prefs = Preferences.userNodeForPackage(JarApiProfilingWindow.class);
|
||||
prefs.put(PREF_KEY, dir.getAbsolutePath());
|
||||
}
|
||||
} catch (IOException | InvalidPathException | SecurityException e) {
|
||||
System.err.println("无法保存目录: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/main/java/org/editing/CodeEditingComponent.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package org.editing;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class CodeEditingComponent extends JTextPane {
|
||||
|
||||
}
|
||||
9
src/main/java/org/editing/Main.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package org.editing;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/icons/class.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
src/main/resources/icons/export.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/main/resources/icons/import.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/main/resources/icons/logo.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
src/main/resources/icons/method.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
src/main/resources/icons/package.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
BIN
src/main/resources/icons/programming/programming.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
src/main/resources/icons/search.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
462
src/main/resources/javascript/HtmlViewer.html
Normal 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>
|
||||