Files
window-axis-innovators-box/javascript/AIaToolbox_light.html
tzdwindows 7 adf659853d feat(browser): 实现Java字体和主题动态同步到HTML界面
- 添加javaFontsLoaded和javaThemeChanged事件监听机制
- 在BrowserWindow和BrowserWindowJDialog中实现字体信息获取和注入
- 前端HTML文件增加对应的字体应用逻辑和样式更新
- 创建WindowRegistry统一管理窗口主题更新
- 更新README文档说明HTML事件使用方法- 支持Monaco和CodeMirror编辑器的字体动态调整
-优化CEF浏览器与Java UI的字体和主题同步流程
2025-10-05 19:49:53 +08:00

718 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>DeepSeek - 智能助手</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- 动态加载主题样式 -->
<link id="hljs-light" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/intellij-light.min.css" media="(prefers-color-scheme: light)">
<link id="hljs-dark" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" media="(prefers-color-scheme: dark)">
<!-- KaTeX 核心样式 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<!-- KaTeX 核心库 -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<!-- 自动渲染扩展 -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
<style>
:root {
--primary-color: #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)
);
}
// 监听Java字体加载事件
document.addEventListener('javaFontsLoaded', function(event) {
const fontInfo = event.detail;
console.log('接收到Java字体信息:', fontInfo);
// 应用Java字体到界面
applyJavaFonts(fontInfo);
});
// 应用Java字体的函数
function applyJavaFonts(fontInfo) {
const uiFonts = fontInfo.uiFonts || {};
const defaultFont = fontInfo.defaultFont || uiFonts['Label.font'] || {};
if (defaultFont && defaultFont.family) {
const fontFamily = defaultFont.family;
const fontSize = defaultFont.size || 14;
const fontWeight = defaultFont.bold ? 'bold' : 'normal';
const fontStyle = defaultFont.italic ? 'italic' : 'normal';
// 创建字体样式
const style = document.createElement('style');
style.textContent = `
body, html {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
.message.user .bubble {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
.message.ai .bubble {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
font-size: ${fontSize}px !important;
}
input, button {
font-family: '${fontFamily}', 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif !important;
}
code, pre {
font-family: 'JetBrains Mono', 'Consolas', 'Monaco', 'Courier New', monospace !important;
}
`;
// 添加到文档头
document.head.appendChild(style);
console.log('Java字体已应用到DeepSeek界面:', fontFamily, fontSize + 'px');
}
}
// 如果字体信息已经存在,立即应用
if (typeof window.javaFontInfo !== 'undefined') {
applyJavaFonts(window.javaFontInfo);
}
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>