chore: import upstream snapshot with attribution
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
# Desktop Font Assets
|
||||
|
||||
These font files are the offline Latin bundle for the vanilla desktop shell.
|
||||
|
||||
## Files
|
||||
|
||||
- `azonix-wordmark.woff2`
|
||||
- Role: brand wordmark only
|
||||
- Source: converted on 2026-05-25 from the local owner-installed file at `~/Library/Fonts/azonix/Azonix.otf`
|
||||
- License: owner-provided / verify before external redistribution
|
||||
- Note: this repo currently treats Azonix as a project-local brand asset
|
||||
|
||||
- `lexend-latin.woff2`
|
||||
- Role: English titles and navigation
|
||||
- Source: Google Fonts CSS2 API, specimen page <https://fonts.google.com/specimen/Lexend>
|
||||
- Downloaded: 2026-05-25
|
||||
- License: SIL Open Font License 1.1
|
||||
|
||||
- `noto-sans-latin.woff2`
|
||||
- Role: English body copy and default Latin UI text
|
||||
- Source: Google Fonts CSS2 API, specimen page <https://fonts.google.com/noto/specimen/Noto+Sans>
|
||||
- Downloaded: 2026-05-25
|
||||
- License: SIL Open Font License 1.1
|
||||
|
||||
- `jetbrains-mono-latin.woff2`
|
||||
- Role: config/value dense controls and numeric surfaces
|
||||
- Source: Google Fonts CSS2 API, specimen page <https://fonts.google.com/specimen/JetBrains+Mono>
|
||||
- Downloaded: 2026-05-25
|
||||
- License: SIL Open Font License 1.1
|
||||
|
||||
## Notes
|
||||
|
||||
- Only Latin subsets are bundled here. Chinese UI text continues to use the existing system fallback stack.
|
||||
- Runtime does not fetch fonts from a CDN. `frontends/desktop/static/assets/fonts/fonts.css` is the only font entrypoint for the vanilla shell.
|
||||
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
@font-face {
|
||||
font-family: "GA Azonix";
|
||||
src:
|
||||
url("./azonix-wordmark.woff2") format("woff2"),
|
||||
local("Azonix"),
|
||||
local("Azonix Regular"),
|
||||
local("Azonix-Regular");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "GA JetBrains Mono";
|
||||
src: url("./jetbrains-mono-latin.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 400 600;
|
||||
font-display: swap;
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,132 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>GenericAgent — Setup</title>
|
||||
<style>
|
||||
:root{--bg:#fff;--text:#0a0a0a;--muted:#6b6b6b;--border:#e8e8e8;--accent:#0a0a0a;--accent-text:#fff;--radius:10px;--font:-apple-system,BlinkMacSystemFont,"SF Pro Text","Helvetica Neue",Helvetica,Arial,sans-serif}
|
||||
@media(prefers-color-scheme:dark){:root{--bg:#1a1a1a;--text:#ededed;--muted:#a0a0a0;--border:#2a2a2a;--accent:#ededed;--accent-text:#0a0a0a}}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;height:100%;font-family:var(--font);background:var(--bg);color:var(--text);display:flex;align-items:center;justify-content:center}
|
||||
.card{max-width:480px;width:90%;padding:2.5rem;border:1px solid var(--border);border-radius:var(--radius);text-align:center}
|
||||
h1{font-size:1.4rem;margin:0 0 .5rem}
|
||||
p{color:var(--muted);font-size:.9rem;margin:.4rem 0;line-height:1.5}
|
||||
.input-row{display:flex;gap:8px;margin-top:1.2rem}
|
||||
input[type=text]{flex:1;padding:10px 12px;border:1px solid var(--border);border-radius:6px;font-size:.9rem;background:var(--bg);color:var(--text);outline:none}
|
||||
input[type=text]:focus{border-color:var(--accent)}
|
||||
button{padding:10px 18px;border:none;border-radius:6px;font-size:.9rem;cursor:pointer;background:var(--accent);color:var(--accent-text);font-weight:500}
|
||||
button:hover{opacity:.85}
|
||||
button:disabled{opacity:.4;cursor:not-allowed}
|
||||
.field-label{display:block;text-align:left;font-size:.8rem;font-weight:500;margin-top:1rem;margin-bottom:.3rem;color:var(--text)}
|
||||
#status{margin-top:1rem;font-size:.85rem;min-height:1.2em}
|
||||
.err{color:#dc2626}.ok{color:#16a34a}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>⚙️ Setup Required</h1>
|
||||
<p>The backend could not start. Please configure the paths below.</p>
|
||||
<p>后端启动失败,请配置以下路径。</p>
|
||||
|
||||
<label class="field-label">Python interpreter 解释器路径</label>
|
||||
<div class="input-row">
|
||||
<input id="pypath" type="text" placeholder="python / path to interpreter" spellcheck="false">
|
||||
</div>
|
||||
<p style="color:var(--muted);font-size:.75rem;margin-top:.3rem">
|
||||
e.g. <code>C:/Python312/python.exe</code> / <code>~/miniconda3/envs/myenv/bin/python</code>
|
||||
</p>
|
||||
|
||||
<label class="field-label">Project directory 项目目录</label>
|
||||
<div class="input-row">
|
||||
<input id="projdir" type="text" placeholder="path to GenericAgent folder" spellcheck="false">
|
||||
</div>
|
||||
<p style="color:var(--muted);font-size:.75rem;margin-top:.3rem">
|
||||
The folder containing <code>frontends/desktop_bridge.py</code>
|
||||
</p>
|
||||
|
||||
<button id="start-btn" onclick="doStart()" style="margin-top:1.2rem;width:100%">Start 启动</button>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
<script>
|
||||
const {invoke} = window.__TAURI__.core;
|
||||
const {getCurrentWindow, Window} = window.__TAURI__.window;
|
||||
const statusEl = document.getElementById('status');
|
||||
const btn = document.getElementById('start-btn');
|
||||
const pyInput = document.getElementById('pypath');
|
||||
const dirInput = document.getElementById('projdir');
|
||||
|
||||
// Pre-fill from Rust config discovery
|
||||
invoke('get_config').then(([py, dir]) => {
|
||||
if (py) pyInput.value = py;
|
||||
if (dir) dirInput.value = dir;
|
||||
}).catch(()=>{});
|
||||
|
||||
async function doStart() {
|
||||
const pythonPath = pyInput.value.trim();
|
||||
const projectDir = dirInput.value.trim();
|
||||
if (!pythonPath) { statusEl.className='err'; statusEl.textContent='Please enter Python path / 请输入 Python 路径'; return; }
|
||||
if (!projectDir) { statusEl.className='err'; statusEl.textContent='Please enter project directory / 请输入项目目录'; return; }
|
||||
btn.disabled = true;
|
||||
statusEl.className=''; statusEl.textContent='Starting bridge… 正在启动…';
|
||||
try {
|
||||
await invoke('start_bridge_with_config', {pythonPath, projectDir});
|
||||
statusEl.className='ok'; statusEl.textContent='Connected! 已连接,正在打开主窗口…';
|
||||
// Show main window and hide setup
|
||||
const mainWin = await Window.getByLabel('main');
|
||||
if (mainWin) {
|
||||
await mainWin.show();
|
||||
await mainWin.setFocus();
|
||||
}
|
||||
const setupWin = getCurrentWindow();
|
||||
await setupWin.hide();
|
||||
} catch(e) {
|
||||
statusEl.className='err'; statusEl.textContent='Failed: '+e;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
pyInput.addEventListener('keydown', e=>{ if(e.key==='Enter') dirInput.focus(); });
|
||||
dirInput.addEventListener('keydown', e=>{ if(e.key==='Enter') doStart(); });
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Input length limit (300 chars) — in separate script to work even without Tauri
|
||||
const MAX_LEN = 300;
|
||||
const _pyInput = document.getElementById('pypath');
|
||||
const _dirInput = document.getElementById('projdir');
|
||||
[_pyInput, _dirInput].forEach(el => {
|
||||
if (!el) return;
|
||||
const hint = document.createElement('span');
|
||||
hint.style.cssText = 'color:#dc2626;font-size:.75rem;display:none;margin-top:2px';
|
||||
el.parentNode.appendChild(hint);
|
||||
|
||||
function checkLimit() {
|
||||
if (el.value.length > MAX_LEN) {
|
||||
el.value = el.value.slice(0, MAX_LEN);
|
||||
}
|
||||
if (el.value.length >= MAX_LEN) {
|
||||
hint.textContent = 'Character limit reached (' + MAX_LEN + ') / 已达字数上限(' + MAX_LEN + ')';
|
||||
hint.style.display = 'block';
|
||||
} else {
|
||||
hint.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Intercept paste: only take first MAX_LEN chars from clipboard
|
||||
el.addEventListener('paste', e => {
|
||||
e.preventDefault();
|
||||
const text = (e.clipboardData || window.clipboardData).getData('text') || '';
|
||||
const start = el.selectionStart;
|
||||
const end = el.selectionEnd;
|
||||
const remaining = MAX_LEN - el.value.length + (end - start);
|
||||
const insert = text.slice(0, Math.max(0, remaining));
|
||||
el.value = el.value.slice(0, start) + insert + el.value.slice(end);
|
||||
el.setSelectionRange(start + insert.length, start + insert.length);
|
||||
checkLimit();
|
||||
});
|
||||
|
||||
// Fallback: keyboard input beyond limit
|
||||
el.addEventListener('input', checkLimit);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,142 @@
|
||||
// GenericAgent Web2 browser bridge adapter.
|
||||
// HTTP is the command/data channel. WebSocket only carries small state events.
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
const listeners = new Map();
|
||||
let ws = null;
|
||||
let cachedBridgeReady = null;
|
||||
const bridgeBase = `${location.protocol}//${location.hostname}:14168`;
|
||||
const wsUrl = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.hostname}:14168/ws`;
|
||||
|
||||
function on(channel, cb) {
|
||||
if (typeof cb !== 'function') return () => {};
|
||||
if (!listeners.has(channel)) listeners.set(channel, new Set());
|
||||
listeners.get(channel).add(cb);
|
||||
if (channel === 'bridge-ready' && cachedBridgeReady) {
|
||||
try { cb(cachedBridgeReady); } catch (err) { console.error('[ga-web2 listener] replay bridge-ready', err); }
|
||||
}
|
||||
return () => listeners.get(channel)?.delete(cb);
|
||||
}
|
||||
|
||||
function emit(channel, payload) {
|
||||
if (channel === 'bridge-ready') cachedBridgeReady = payload;
|
||||
const set = listeners.get(channel);
|
||||
if (!set) return;
|
||||
for (const cb of Array.from(set)) {
|
||||
try { cb(payload); } catch (err) { console.error('[ga-web2 listener]', channel, err); }
|
||||
}
|
||||
}
|
||||
|
||||
async function http(path, options = {}) {
|
||||
const headers = Object.assign({}, options.headers || {});
|
||||
const init = Object.assign({}, options, { headers });
|
||||
if (init.body && typeof init.body !== 'string') {
|
||||
headers['Content-Type'] = headers['Content-Type'] || 'application/json';
|
||||
init.body = JSON.stringify(init.body);
|
||||
}
|
||||
const res = await fetch(`${bridgeBase}${path}`, init);
|
||||
const text = await res.text();
|
||||
let data = null;
|
||||
try { data = text ? JSON.parse(text) : {}; } catch (_) { data = { raw: text }; }
|
||||
if (!res.ok) {
|
||||
const err = new Error((data && (data.error || data.message)) || `${res.status} ${res.statusText}`);
|
||||
err.status = res.status;
|
||||
err.data = data;
|
||||
throw err;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function connectWs() {
|
||||
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
|
||||
try {
|
||||
ws = new WebSocket(wsUrl);
|
||||
ws.addEventListener('open', () => emit('bridge-log', 'WS state channel connected'));
|
||||
ws.addEventListener('message', (ev) => {
|
||||
let msg;
|
||||
try { msg = JSON.parse(ev.data); } catch (_) { return; }
|
||||
if (msg.type === 'bridge-ready') {
|
||||
emit('bridge-ready', msg);
|
||||
} else if (msg.type === 'session-state') {
|
||||
emit('bridge-notification', msg);
|
||||
} else if (msg.type === 'bridge-log') {
|
||||
emit('bridge-log', msg.payload || msg);
|
||||
} else if (msg.type === 'bridge-error') {
|
||||
emit('bridge-error', msg.payload || msg);
|
||||
}
|
||||
});
|
||||
ws.addEventListener('close', () => emit('bridge-closed', { reason: 'ws-closed' }));
|
||||
ws.addEventListener('error', () => emit('bridge-error', { type: 'ws-error', message: 'WebSocket state channel error' }));
|
||||
} catch (err) {
|
||||
emit('bridge-error', { type: 'ws-error', message: err.message || String(err) });
|
||||
}
|
||||
}
|
||||
|
||||
async function rpc(method, params = {}) {
|
||||
switch (method) {
|
||||
case 'app/status':
|
||||
return http('/status');
|
||||
case 'app/config/get':
|
||||
return http('/config');
|
||||
case 'app/config/save':
|
||||
return http('/config', { method: 'POST', body: params || {} });
|
||||
case 'get/model-profiles':
|
||||
return http('/model-profiles');
|
||||
case 'session/new':
|
||||
return http('/session/new', { method: 'POST', body: params || {} });
|
||||
case 'session/prompt': {
|
||||
const sid = params.sessionId || params.id || params.bridgeSessionId;
|
||||
if (!sid) throw new Error('session/prompt missing sessionId');
|
||||
return http(`/session/${encodeURIComponent(sid)}/prompt`, { method: 'POST', body: params || {} });
|
||||
}
|
||||
case 'session/poll': {
|
||||
const sid = params.sessionId || params.id || params.bridgeSessionId;
|
||||
if (!sid) throw new Error('session/poll missing sessionId');
|
||||
const after = params.afterId ?? params.after ?? 0;
|
||||
const limit = params.limit ?? 200;
|
||||
return http(`/session/${encodeURIComponent(sid)}/messages?after=${encodeURIComponent(after)}&limit=${encodeURIComponent(limit)}`);
|
||||
}
|
||||
case 'session/cancel': {
|
||||
const sid = params.sessionId || params.id || params.bridgeSessionId;
|
||||
if (!sid) throw new Error('session/cancel missing sessionId');
|
||||
return http(`/session/${encodeURIComponent(sid)}/cancel`, { method: 'POST', body: params || {} });
|
||||
}
|
||||
case 'app/path/open':
|
||||
return http('/path/open', { method: 'POST', body: params || {} });
|
||||
case 'app/path/selectGaRoot':
|
||||
return http('/config');
|
||||
case 'list_continuable_sessions':
|
||||
return { sessions: [] };
|
||||
case 'restore_session':
|
||||
throw new Error('restore_session is not implemented in web2 bridge');
|
||||
default:
|
||||
throw new Error(`Unknown RPC method: ${method}`);
|
||||
}
|
||||
}
|
||||
|
||||
window.ga = {
|
||||
platform: navigator.platform.toLowerCase().includes('mac') ? 'darwin' : 'win32',
|
||||
startBridge: async () => { connectWs(); return http('/status'); },
|
||||
stopBridge: async () => ({ ok: true }),
|
||||
checkStatus: () => rpc('app/status', {}),
|
||||
getConfig: () => rpc('app/config/get', {}),
|
||||
saveConfig: (cfg) => rpc('app/config/save', cfg || {}),
|
||||
getModelProfiles: () => rpc('get/model-profiles', {}),
|
||||
selectGaRoot: () => rpc('app/path/selectGaRoot', {}),
|
||||
openMykeyTemplate: () => rpc('app/path/open', { kind: 'mykeyTemplate' }),
|
||||
openMykey: () => rpc('app/path/open', { kind: 'mykey' }),
|
||||
pollSession: (sessionId, afterId = 0) => rpc('session/poll', { sessionId, afterId }),
|
||||
rpc,
|
||||
onBridgeMessage: (cb) => on('bridge-message', cb),
|
||||
onBridgeNotification: (cb) => on('bridge-notification', cb),
|
||||
onBridgeError: (cb) => on('bridge-error', cb),
|
||||
onBridgeClosed: (cb) => on('bridge-closed', cb),
|
||||
onBridgeReady: (cb) => on('bridge-ready', cb),
|
||||
onBridgeLog: (cb) => on('bridge-log', cb),
|
||||
onOpenSearch: (cb) => on('open-search', cb)
|
||||
};
|
||||
|
||||
connectWs();
|
||||
http('/status').then(status => emit('bridge-ready', status)).catch(err => emit('bridge-error', { type: 'http-error', message: err.message || String(err) }));
|
||||
})();
|
||||
@@ -0,0 +1,679 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>GenericAgent</title>
|
||||
<link rel="stylesheet" href="assets/fonts/fonts.css?v=2">
|
||||
<link rel="stylesheet" href="styles.css?v=181">
|
||||
<!-- KaTeX: LaTeX 公式渲染 -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" crossorigin="anonymous">
|
||||
<!-- highlight.js: 代码高亮 -->
|
||||
<link id="hljs-theme" rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css" crossorigin="anonymous">
|
||||
<!-- Flatpickr: 日期选择器 -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/l10n/zh.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
(function(){
|
||||
var t=localStorage.getItem('ga_theme'); if(t) document.documentElement.dataset.theme=t;
|
||||
var l=localStorage.getItem('ga_lang'); if(l==='en') document.documentElement.lang='en';
|
||||
var legacy=localStorage.getItem('ga_style');
|
||||
var app=localStorage.getItem('ga_appearance');
|
||||
if(!app&&legacy){
|
||||
if(legacy==='dark') app='dark';
|
||||
else{ app='light'; if(legacy==='classic') localStorage.setItem('ga_plain','1'); }
|
||||
localStorage.setItem('ga_appearance',app);
|
||||
localStorage.removeItem('ga_style');
|
||||
}
|
||||
app=app||'light';
|
||||
document.documentElement.dataset.appearance=app;
|
||||
if(app==='light'&&localStorage.getItem('ga_plain')==='1') document.documentElement.dataset.plain='1';
|
||||
var LEGACY_FONT={sm:12,md:14,lg:16};
|
||||
function chatFontPx(v){
|
||||
if(v&&LEGACY_FONT[v]) return LEGACY_FONT[v];
|
||||
var n=parseInt(v,10); if(!isNaN(n)&&n>=10&&n<=20) return n;
|
||||
return 14;
|
||||
}
|
||||
var fs=localStorage.getItem('ga_font_size');
|
||||
var cfp=chatFontPx(fs);
|
||||
document.documentElement.dataset.chatFont=String(cfp);
|
||||
document.documentElement.style.setProperty('--chat-font',cfp+'px');
|
||||
var hljs=document.getElementById('hljs-theme');
|
||||
if(hljs&&app==='dark') hljs.href='https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css';
|
||||
try{ var sraw=localStorage.getItem('ga_sessions'); if(sraw && JSON.parse(sraw).length) document.documentElement.dataset.bootHasSessions='1'; }catch(_){}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
|
||||
<div class="body">
|
||||
|
||||
<!-- ① 左侧边栏 -->
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-text">
|
||||
<div class="brand-name ga-wordmark">Generic Agent</div>
|
||||
<div class="brand-sub" data-i18n="brand.sub"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav" id="nav">
|
||||
<a class="nav-item active" data-page="chat">
|
||||
<span class="ic" data-ga-icon="chatTeardropText"></span>
|
||||
<span data-i18n="nav.chat"></span>
|
||||
</a>
|
||||
<a class="nav-item" data-page="collab">
|
||||
<span class="ic" data-ga-icon="gitFork"></span>
|
||||
<span data-i18n="nav.collab"></span>
|
||||
<span id="collab-badge" class="collab-nav-badge" hidden></span>
|
||||
</a>
|
||||
<a class="nav-item" data-page="services">
|
||||
<span class="ic" data-ga-icon="broadcast"></span>
|
||||
<span data-i18n="nav.services"></span>
|
||||
</a>
|
||||
<a class="nav-item" data-page="token">
|
||||
<span class="ic" data-ga-icon="coins"></span>
|
||||
<span data-i18n="nav.token"></span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<!-- 快速接入官方模型(DeepSeek / 通义千问):填好 API Key 即可使用 -->
|
||||
<div class="provider-quickstart" id="provider-quickstart">
|
||||
<div class="pq-head" id="pq-toggle" role="button" tabindex="0" aria-expanded="true">
|
||||
<span class="fr-l">
|
||||
<span class="ic" data-ga-icon="lightning"></span>
|
||||
<span class="pq-title" data-i18n="pq.title"></span>
|
||||
</span>
|
||||
<span class="pq-collapse chev" data-ga-icon="caretRight" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="pq-body">
|
||||
<button type="button" class="pq-btn" data-provider="deepseek">
|
||||
<span class="pq-logo" style="background:rgba(77,107,254,.12)" aria-hidden="true"><svg viewBox="0 0 24 24" fill="#4D6BFE" xmlns="http://www.w3.org/2000/svg"><path d="M23.748 4.651c-.254-.124-.364.113-.512.233-.051.04-.094.09-.137.137-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.155-.708-.311-.955-.65-.172-.24-.219-.509-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.094.172.187.129.323-.082.28-.18.553-.266.833-.055.179-.137.218-.328.14a5.5 5.5 0 0 1-1.737-1.179c-.857-.828-1.631-1.743-2.597-2.46a12 12 0 0 0-.689-.47c-.985-.957.13-1.743.387-1.836.27-.098.094-.433-.778-.428-.872.003-1.67.295-2.687.685a3 3 0 0 1-.465.136 9.6 9.6 0 0 0-2.883-.101c-1.885.21-3.39 1.1-4.497 2.622C.082 8.776-.231 10.854.152 13.02c.403 2.284 1.568 4.175 3.36 5.653 1.857 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.132-.284 4.994-1.86.47.234.962.328 1.78.398.629.058 1.235-.031 1.705-.129.735-.155.684-.836.418-.961-2.155-1.004-1.682-.595-2.112-.926 1.095-1.295 2.768-3.598 3.284-6.733.05-.346.115-.834.108-1.114-.004-.171.035-.238.23-.257a4.2 4.2 0 0 0 1.545-.475c1.397-.763 1.96-2.016 2.093-3.517.02-.23-.004-.467-.247-.588M11.58 18.168c-2.088-1.642-3.101-2.183-3.52-2.16-.39.024-.32.472-.234.763.09.288.207.487.371.74.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.168-1.361-.801-2.5-1.86-3.301-3.306-.775-1.393-1.225-2.888-1.299-4.482-.02-.385.094-.522.477-.592a4.7 4.7 0 0 1 1.53-.038c2.131.311 3.946 1.264 5.467 2.774.868.86 1.525 1.887 2.202 2.89.72 1.066 1.494 2.082 2.48 2.915.348.291.626.513.892.677-.802.09-2.14.109-3.055-.615zm1.001-6.44a.306.306 0 0 1 .415-.287.3.3 0 0 1 .113.074.3.3 0 0 1 .086.214c0 .17-.136.307-.308.307a.303.303 0 0 1-.306-.307m3.11 1.596c-.2.081-.4.151-.591.16a1.25 1.25 0 0 1-.798-.254c-.274-.23-.47-.358-.551-.758a1.7 1.7 0 0 1 .015-.588c.07-.327-.007-.537-.238-.727-.188-.156-.426-.199-.689-.199a.6.6 0 0 1-.254-.078.253.253 0 0 1-.114-.358 1 1 0 0 1 .192-.21c.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.392.451.462.576.685.915.176.264.336.536.446.848.066.194-.02.353-.25.45"/></svg></span>
|
||||
<span class="pq-btn-text">
|
||||
<span class="pq-btn-name">DeepSeek</span>
|
||||
<span class="pq-btn-desc" data-i18n="pq.deepseekDesc"></span>
|
||||
</span>
|
||||
<span class="pq-arrow" data-ga-icon="caretRight"></span>
|
||||
</button>
|
||||
<button type="button" class="pq-btn" data-provider="qwen">
|
||||
<span class="pq-logo" style="background:rgba(97,92,237,.12)" aria-hidden="true"><svg viewBox="0 0 24 24" fill="#615CED" xmlns="http://www.w3.org/2000/svg"><path d="M23.919 14.545 20.817 9.17l1.47-2.544a.56.56 0 0 0 0-.566l-1.633-2.83a.57.57 0 0 0-.49-.283h-6.207L12.487.402a.57.57 0 0 0-.49-.284H8.732a.56.56 0 0 0-.49.284L5.139 5.775h-2.94a.56.56 0 0 0-.49.284L.077 8.887a.56.56 0 0 0 0 .567L3.18 14.83l-1.47 2.545a.56.56 0 0 0 0 .566l1.634 2.83a.57.57 0 0 0 .49.283h6.205l1.47 2.545a.57.57 0 0 0 .49.284h3.266a.57.57 0 0 0 .49-.284l3.104-5.375h2.94a.57.57 0 0 0 .49-.283l1.634-2.828a.55.55 0 0 0-.004-.568M8.733.686l1.634 2.828-1.634 2.828H21.8L20.164 9.17H7.425L5.63 6.06Zm1.306 19.801-6.205-.002 1.634-2.83h3.265L2.201 6.344h3.267q3.182 5.517 6.367 11.032zm10.124-5.66L18.53 12l-6.532 11.315-1.634-2.83c2.129-3.673 4.25-7.351 6.373-11.028h3.592l3.102 5.374z"/></svg></span>
|
||||
<span class="pq-btn-text">
|
||||
<span class="pq-btn-name">通义千问</span>
|
||||
<span class="pq-btn-desc" data-i18n="pq.qwenDesc"></span>
|
||||
</span>
|
||||
<span class="pq-arrow" data-ga-icon="caretRight"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-foot">
|
||||
<a class="foot-row" id="settings-btn">
|
||||
<span class="fr-l">
|
||||
<span class="ic" data-ga-icon="gear"></span>
|
||||
<span data-i18n="foot.settings"></span>
|
||||
</span>
|
||||
<span class="chev" data-ga-icon="caretRight"></span>
|
||||
</a>
|
||||
<div class="foot-ver" data-i18n="foot.ver"></div>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="sb-resize" id="sb-resize"></div>
|
||||
|
||||
<!-- 中间主区 -->
|
||||
<main class="main">
|
||||
|
||||
<div id="pages">
|
||||
|
||||
<!-- 聊天 -->
|
||||
<section class="page active page--chat-ui" data-page="chat">
|
||||
<div class="page-top">
|
||||
<button type="button" class="pt-icon pt-sb-toggle" aria-label="toggle sidebar">
|
||||
<span data-ga-icon="caretLeft"></span>
|
||||
</button>
|
||||
<button class="run-state" id="run-toggle" type="button">
|
||||
<i class="dot"></i> <span class="rs-label"></span>
|
||||
</button>
|
||||
<button type="button" class="pt-icon pt-rp-toggle" aria-label="toggle conversations">
|
||||
<span data-ga-icon="caretRight"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="chat-shell">
|
||||
<div class="chat-panel">
|
||||
<div class="msg-area">
|
||||
<div class="chat-start">
|
||||
<div class="feature-head">
|
||||
<div class="page-title" data-i18n="chat.startTitle"></div>
|
||||
<div class="page-sub" data-i18n="chat.startSub"></div>
|
||||
</div>
|
||||
<div class="feature-grid"></div>
|
||||
</div>
|
||||
<div class="session-loading" id="session-loading" hidden aria-live="polite">
|
||||
<span class="ga-spin" aria-hidden="true"></span>
|
||||
<span data-i18n="chat.sessionLoading"></span>
|
||||
</div>
|
||||
<div class="msg-loading" id="msg-loading" hidden aria-live="polite">
|
||||
<span class="ga-spin" aria-hidden="true"></span>
|
||||
<span data-i18n="chat.interrupting"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="composer-anchor" id="chat-composer-anchor">
|
||||
<div class="composer" id="chat-composer" data-composer-ctx="chat">
|
||||
<div class="composer-slot">
|
||||
<div class="plan-card" id="plan-bar" hidden aria-live="polite"></div>
|
||||
<div class="composer-inset">
|
||||
<div class="thumb-strip" id="thumb-strip" hidden></div>
|
||||
<div class="input" id="chat-input" contenteditable="true" role="textbox" aria-multiline="true" data-i18n-ph="composer.placeholder"></div>
|
||||
<div class="composer-bot">
|
||||
<div class="composer-plus-wrap">
|
||||
<button type="button" class="ic-btn composer-plus" id="chat-plus-btn" aria-haspopup="menu" aria-expanded="false" data-i18n-title="collab.plusMenu" title="">
|
||||
<span data-ga-icon="plus"></span>
|
||||
</button>
|
||||
<div class="ga-menu composer-menu" id="chat-menu" hidden role="menu">
|
||||
<button type="button" class="ga-menu-item" data-composer-action="upload" role="menuitem">
|
||||
<span data-ga-icon="paperclip"></span>
|
||||
<span data-i18n="upload.button"></span>
|
||||
</button>
|
||||
<button type="button" class="ga-menu-item" data-composer-action="preset" role="menuitem">
|
||||
<span data-ga-icon="lightning"></span>
|
||||
<span data-i18n="modal.preset"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<span class="spacer"></span>
|
||||
<button type="button" class="chip" id="model-chip" data-i18n-title="model.menuLabel" title=""><span class="model-name"></span> <span class="chip-ic" data-ga-icon="caretDown"></span></button>
|
||||
<button type="button" class="send" id="send-btn" aria-label="send">
|
||||
<span data-ga-icon="paperPlaneTilt"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ga-menu" id="model-menu" hidden></div>
|
||||
<input type="file" id="chat-file-input" multiple hidden>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 后台服务:消息通道 + 状态面板 -->
|
||||
<section class="page" data-page="services">
|
||||
<div class="page-top">
|
||||
<button type="button" class="pt-icon pt-sb-toggle" aria-label="toggle sidebar">
|
||||
<span data-ga-icon="caretLeft"></span>
|
||||
</button>
|
||||
<span class="pt-spacer"></span>
|
||||
<button type="button" class="pt-icon pt-rp-toggle" aria-label="toggle conversations">
|
||||
<span data-ga-icon="caretRight"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="page-pad">
|
||||
<div class="page-title" data-i18n="page.services.title"></div>
|
||||
<div class="svc-tabs" id="svc-tabs">
|
||||
<button type="button" class="svc-tab active" data-tab="channels" data-i18n="page.channels.title"></button>
|
||||
<button type="button" class="svc-tab" data-tab="status" data-i18n="page.status.title"></button>
|
||||
</div>
|
||||
<div class="svc-panel active" data-svc-panel="channels">
|
||||
<div class="list" id="chan-list"></div>
|
||||
<div class="list-empty" id="chan-empty" hidden data-i18n="ch.empty"></div>
|
||||
</div>
|
||||
<div class="svc-panel" data-svc-panel="status">
|
||||
<div class="list" id="status-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 指挥家 Conductor(对话区结构复用 page--chat-ui / chat-shell) -->
|
||||
<section class="page page--chat-ui" data-page="collab">
|
||||
<div class="page-top">
|
||||
<button type="button" class="pt-icon pt-sb-toggle" aria-label="toggle sidebar">
|
||||
<span data-ga-icon="caretLeft"></span>
|
||||
</button>
|
||||
<div class="page-top-center">
|
||||
<button class="run-state" id="collab-run-toggle" type="button">
|
||||
<i class="dot"></i> <span class="rs-label"></span>
|
||||
</button>
|
||||
<button type="button" class="pt-icon collab-retry-btn" id="collab-retry" hidden data-i18n-aria-label="collab.retry" aria-label="retry">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="pt-icon pt-rp-toggle" aria-label="toggle conversations">
|
||||
<span data-ga-icon="caretRight"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="chat-shell">
|
||||
<div class="chat-panel">
|
||||
<div class="msg-area collab-msgs" id="collab-msgs">
|
||||
<div class="collab-scroll">
|
||||
<div class="collab-guide" id="collab-welcome" hidden>
|
||||
<div class="feature-head">
|
||||
<div class="page-title" data-i18n="collab.guideTitle"></div>
|
||||
<p class="page-sub" data-i18n="collab.guideWhen"></p>
|
||||
</div>
|
||||
<div class="collab-guide-steps">
|
||||
<div class="collab-guide-step">
|
||||
<span class="collab-guide-ic" aria-hidden="true"><span data-ga-icon="chatTeardropText"></span></span>
|
||||
<span class="collab-guide-txt"><span class="collab-guide-label" data-i18n="collab.guideStep1t"></span><span class="collab-guide-desc" data-i18n="collab.guideStep1d"></span></span>
|
||||
</div>
|
||||
<div class="collab-guide-step">
|
||||
<span class="collab-guide-ic" aria-hidden="true"><span data-ga-icon="gridFour"></span></span>
|
||||
<span class="collab-guide-txt"><span class="collab-guide-label" data-i18n="collab.guideStep2t"></span><span class="collab-guide-desc" data-i18n="collab.guideStep2d"></span></span>
|
||||
</div>
|
||||
<div class="collab-guide-step">
|
||||
<span class="collab-guide-ic" aria-hidden="true"><span data-ga-icon="listChecks"></span></span>
|
||||
<span class="collab-guide-txt"><span class="collab-guide-label" data-i18n="collab.guideStep3t"></span><span class="collab-guide-desc" data-i18n="collab.guideStep3d"></span></span>
|
||||
</div>
|
||||
<div class="collab-guide-step">
|
||||
<span class="collab-guide-ic" aria-hidden="true"><span data-ga-icon="pencilSimple"></span></span>
|
||||
<span class="collab-guide-txt"><span class="collab-guide-label" data-i18n="collab.guideStep4t"></span><span class="collab-guide-desc" data-i18n="collab.guideStep4d"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msgs" id="collab-msg-list" hidden></div>
|
||||
</div>
|
||||
<div class="collab-overlay">
|
||||
<div class="collab-rail" id="collab-rail" hidden>
|
||||
<button type="button" class="collab-rail-handle" id="collab-rail-toggle" data-i18n-title="collab.showProgressTitle" title="">
|
||||
<span class="collab-rail-grip" aria-hidden="true"></span>
|
||||
<svg class="collab-rail-chev" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"/></svg>
|
||||
<span class="collab-rail-badge collab-rail-run" id="collab-rail-run" hidden>
|
||||
<span class="collab-rail-spin" aria-hidden="true"></span>
|
||||
<span class="n" id="collab-rail-run-n">0</span>
|
||||
</span>
|
||||
<span class="collab-rail-badge collab-rail-done" id="collab-rail-done" hidden>
|
||||
<span class="collab-rail-dot" aria-hidden="true"></span>
|
||||
<span class="n" id="collab-rail-done-n">0</span>
|
||||
</span>
|
||||
<span class="collab-rail-badge collab-rail-issue" id="collab-rail-issue" hidden>
|
||||
<span class="collab-rail-dot collab-rail-dot--danger" aria-hidden="true"></span>
|
||||
<span class="n" id="collab-rail-issue-n">0</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<aside class="collab-prog-panel" id="collab-prog-panel">
|
||||
<div class="collab-prog-panel-head">
|
||||
<span data-i18n="collab.progressTitle"></span>
|
||||
<button type="button" class="ic-btn collab-prog-close" id="collab-prog-close" aria-label="close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="collab-prog-stats" id="collab-progress-stats" hidden></div>
|
||||
<div class="collab-progress-empty" id="collab-progress-empty" data-i18n="collab.progressEmpty"></div>
|
||||
<div id="collab-workers"></div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collab-quick" id="collab-quick">
|
||||
<button type="button" class="pill" data-prompt-key="collab.chipProgress" data-i18n="collab.chipProgress"></button>
|
||||
<button type="button" class="pill" data-prompt-key="collab.chipPause" data-i18n="collab.chipPause"></button>
|
||||
<button type="button" class="pill" data-prompt-key="collab.chipSummary" data-i18n="collab.chipSummary"></button>
|
||||
</div>
|
||||
<div class="composer-anchor" id="cdb-composer-anchor">
|
||||
<div class="composer" id="cdb-composer" data-composer-ctx="collab">
|
||||
<div class="composer-slot">
|
||||
<div class="composer-inset">
|
||||
<div class="thumb-strip" id="cdb-thumb-strip" hidden></div>
|
||||
<div class="input" id="cdb-input" contenteditable="true" role="textbox" aria-multiline="true" data-i18n-ph="collab.placeholder"></div>
|
||||
<div class="composer-bot">
|
||||
<div class="composer-plus-wrap">
|
||||
<button type="button" class="ic-btn composer-plus" id="cdb-plus-btn" aria-haspopup="menu" aria-expanded="false" data-i18n-title="collab.plusMenu" title="">
|
||||
<span data-ga-icon="plus"></span>
|
||||
</button>
|
||||
<div class="ga-menu composer-menu" id="cdb-menu" hidden role="menu">
|
||||
<button type="button" class="ga-menu-item" data-composer-action="upload" role="menuitem">
|
||||
<span data-ga-icon="paperclip"></span>
|
||||
<span data-i18n="upload.button"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<span class="spacer"></span>
|
||||
<button type="button" class="chip" id="cdb-model-chip" data-i18n-title="model.menuLabel" title="">
|
||||
<span class="model-name"></span>
|
||||
<span class="chip-ic" data-ga-icon="caretDown"></span>
|
||||
</button>
|
||||
<button type="button" class="send" id="cdb-send" aria-label="send">
|
||||
<span data-ga-icon="paperPlaneTilt"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ga-menu" id="cdb-model-menu" hidden></div>
|
||||
<input type="file" id="cdb-file-input" multiple hidden>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Token 统计 -->
|
||||
<section class="page" data-page="token">
|
||||
<div class="page-top">
|
||||
<button type="button" class="pt-icon pt-sb-toggle" aria-label="toggle sidebar">
|
||||
<span data-ga-icon="caretLeft"></span>
|
||||
</button>
|
||||
<span class="pt-spacer"></span>
|
||||
<button type="button" class="pt-icon pt-rp-toggle" aria-label="toggle conversations">
|
||||
<span data-ga-icon="caretRight"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="page-pad">
|
||||
<div class="page-title" data-i18n="page.token.title"></div>
|
||||
<div class="tok-tabs" id="tok-tabs">
|
||||
<button class="tok-tab active" data-tab="chat" data-i18n="tok.tabAll"></button>
|
||||
<button class="tok-tab" data-tab="conductor" data-i18n="tok.tabConductor"></button>
|
||||
</div>
|
||||
<div class="tok-filter">
|
||||
<label data-i18n="tok.from"></label>
|
||||
<input type="text" id="tok-since" class="tok-date" readonly>
|
||||
<label data-i18n="tok.to"></label>
|
||||
<input type="text" id="tok-until" class="tok-date" readonly>
|
||||
<button class="tok-reset" id="tok-reset" data-i18n="tok.reset"></button>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<div class="stat"><div class="s-n" id="tok-total-n">0</div><div class="s-l" data-i18n="tok.total"></div></div>
|
||||
<div class="stat"><div class="s-n" id="tok-cost-n">0%</div><div class="s-l" data-i18n="tok.cost"></div></div>
|
||||
<div class="stat"><div class="s-n" id="tok-today-n">0</div><div class="s-l" data-i18n="tok.today"></div></div>
|
||||
</div>
|
||||
<table class="tok-table" id="tok-table">
|
||||
<thead><tr>
|
||||
<th data-i18n="tok.colSession"></th>
|
||||
<th data-i18n="tok.colIn"></th>
|
||||
<th data-i18n="tok.colOut"></th>
|
||||
<th data-i18n="tok.colCacheW"></th>
|
||||
<th data-i18n="tok.colCache"></th>
|
||||
<th data-i18n="tok.cost"></th>
|
||||
</tr></thead>
|
||||
<tbody id="tok-tbody"></tbody>
|
||||
</table>
|
||||
<div class="tok-pager" id="tok-pager"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- ④ 右侧会话面板 -->
|
||||
<div class="rp-resize" id="rp-resize"></div>
|
||||
<aside class="rightpanel" id="rightpanel">
|
||||
<div class="rp-head">
|
||||
<div class="search">
|
||||
<span class="search-ic" data-ga-icon="magnifyingGlass"></span>
|
||||
<input data-i18n-ph="search.placeholder" placeholder="" maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="new-conv">
|
||||
<span data-ga-icon="plus"></span>
|
||||
<span data-i18n="conv.new"></span>
|
||||
</button>
|
||||
<div class="conv-list"></div>
|
||||
|
||||
<!-- 会话操作菜单 -->
|
||||
<div class="ctx-menu" id="conv-menu" hidden>
|
||||
<div class="ctx-item" data-act="pin">
|
||||
<span data-ga-icon="pushPinSimple"></span>
|
||||
<span data-i18n="ctx.pin"></span>
|
||||
</div>
|
||||
<div class="ctx-item" data-act="rename">
|
||||
<span data-ga-icon="pencilSimple"></span>
|
||||
<span data-i18n="ctx.rename"></span>
|
||||
</div>
|
||||
<div class="ctx-item danger" data-act="del">
|
||||
<span data-ga-icon="trash"></span>
|
||||
<span data-i18n="ctx.del"></span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 预设功能 弹窗 -->
|
||||
<div class="modal" id="preset-modal" hidden>
|
||||
<div class="modal-backdrop" data-close></div>
|
||||
<div class="modal-card" role="dialog">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" data-i18n="modal.preset"></div>
|
||||
<button id="preset-restore-btn" class="preset-restore" type="button" data-i18n="builtinPreset.restoreBtn" hidden></button>
|
||||
<button class="modal-x" data-close data-i18n-title="common.close" title="">
|
||||
<span data-ga-icon="x"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body preset-pop">
|
||||
<div class="feature-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加模型 弹窗(模板,未实现)-->
|
||||
<div class="modal" id="add-model-modal" hidden>
|
||||
<div class="modal-backdrop" data-close></div>
|
||||
<div class="modal-card" role="dialog">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" id="model-form-title" data-i18n="modal.addModel"></div>
|
||||
<button class="modal-x" data-close data-i18n-title="common.close" title="">
|
||||
<span data-ga-icon="x"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body model-form-body">
|
||||
<div class="model-guide" id="model-guide" hidden>
|
||||
<div class="model-guide-head">
|
||||
<span class="model-guide-logo" id="model-guide-logo" aria-hidden="true"></span>
|
||||
<span class="model-guide-name" id="model-guide-name"></span>
|
||||
</div>
|
||||
<ol class="model-guide-steps">
|
||||
<li data-i18n="guide.step1"></li>
|
||||
<li data-i18n="guide.step2"></li>
|
||||
<li data-i18n="guide.step3"></li>
|
||||
</ol>
|
||||
<div class="model-guide-link-row">
|
||||
<a class="model-guide-link" id="model-guide-link" href="#" target="_blank" rel="noopener noreferrer"></a>
|
||||
<button type="button" class="model-guide-copy" id="model-guide-copy" data-i18n-title="guide.copy" title="" aria-label="copy">
|
||||
<span data-ga-icon="copy"></span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="model-guide-tip" data-i18n="guide.prefillTip"></p>
|
||||
</div>
|
||||
<form id="add-model-form" class="model-form" autocomplete="off">
|
||||
<label><span class="field-label"><span data-i18n="model.model"></span><span class="field-req" aria-hidden="true">*</span></span><input name="model" required data-i18n-ph="model.modelPh" maxlength="50"><span class="field-hint" data-i18n="model.modelHint"></span></label>
|
||||
<label id="model-apikey-label"><span class="field-label"><span data-i18n="model.apikey"></span><span class="field-req" aria-hidden="true">*</span></span><input name="apikey" type="password" data-i18n-ph="model.apikeyPh" id="model-apikey-input" maxlength="200"></label>
|
||||
<label><span class="field-label"><span data-i18n="model.apibase"></span><span class="field-req" aria-hidden="true">*</span></span><input name="apibase" required data-i18n-ph="model.apibasePh" maxlength="200"></label>
|
||||
<div class="model-form-field"><span class="field-label"><span data-i18n="model.protocol"></span><span class="field-req" aria-hidden="true">*</span></span>
|
||||
<div class="seg-group" role="radiogroup">
|
||||
<label class="seg-opt"><input type="radio" name="protocol" value="oai" required><span data-i18n="model.protocolOai"></span></label>
|
||||
<label class="seg-opt"><input type="radio" name="protocol" value="claude" required><span data-i18n="model.protocolClaude"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-form-field"><span class="field-label"><span data-i18n="model.stream"></span></span>
|
||||
<div class="seg-group" role="radiogroup">
|
||||
<label class="seg-opt"><input type="radio" name="stream" value="true" checked><span data-i18n="model.streamOn"></span></label>
|
||||
<label class="seg-opt"><input type="radio" name="stream" value="false"><span data-i18n="model.streamOff"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
<label><span data-i18n="model.name"></span><input name="name" data-i18n-ph="model.namePh" data-optional-ph maxlength="50"></label>
|
||||
<div class="model-form-row">
|
||||
<label class="model-form-mini"><span data-i18n="model.retries"></span><input name="max_retries" type="text" inputmode="numeric" pattern="[0-9]*" min="1" max="20" value="5"></label>
|
||||
<label class="model-form-mini"><span data-i18n="model.connTimeout"></span><input name="connect_timeout" type="text" inputmode="numeric" pattern="[0-9]*" min="1" max="300" value="15"></label>
|
||||
<label class="model-form-mini"><span data-i18n="model.readTimeout"></span><input name="read_timeout" type="text" inputmode="numeric" pattern="[0-9]*" min="5" max="3600" value="300"></label>
|
||||
</div>
|
||||
<p class="model-form-err" id="add-model-err" hidden></p>
|
||||
<div class="model-form-actions">
|
||||
<button type="button" class="set-btn ghost" data-close data-i18n="common.cancel"></button>
|
||||
<button type="submit" class="set-btn primary"><span data-i18n="model.save"></span></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="confirm-modal" hidden>
|
||||
<div class="modal-backdrop" data-close></div>
|
||||
<div class="modal-card confirm-card" role="dialog" aria-modal="true" aria-labelledby="confirm-title">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" id="confirm-title"></div>
|
||||
<button class="modal-x" data-close data-i18n-title="common.close" title="">
|
||||
<span data-ga-icon="x"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body confirm-body">
|
||||
<div class="confirm-message" id="confirm-message"></div>
|
||||
<div class="confirm-actions">
|
||||
<button type="button" class="set-btn ghost" id="confirm-cancel"></button>
|
||||
<button type="button" class="set-btn primary" id="confirm-ok"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义预设 弹窗 -->
|
||||
<div class="modal" id="custom-preset-modal" hidden>
|
||||
<div class="modal-backdrop" data-close></div>
|
||||
<div class="modal-card" role="dialog">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" data-i18n="modal.customPreset"></div>
|
||||
<button class="modal-x" data-close data-i18n-title="common.close" title="">
|
||||
<span data-ga-icon="x"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body cp-body">
|
||||
<input class="cp-input" id="cp-title" type="text" data-i18n-ph="customPreset.titlePh" placeholder="" maxlength="50">
|
||||
<textarea class="cp-textarea" id="cp-prompt" rows="6" data-i18n-ph="customPreset.promptPh" placeholder="" maxlength="10000"></textarea>
|
||||
<div class="cp-error" id="cp-error" hidden></div>
|
||||
<div class="cp-actions">
|
||||
<button class="cp-btn" data-close data-i18n="common.cancel" type="button"></button>
|
||||
<button class="cp-btn cp-btn-primary" id="cp-save" data-i18n="common.save" type="button"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 渠道日志 弹窗 -->
|
||||
<div class="modal" id="chan-log-modal" hidden>
|
||||
<div class="modal-backdrop" data-close></div>
|
||||
<div class="modal-card modal-card-wide modal-card-inset" role="dialog">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" id="chan-log-title" data-i18n="modal.channelLogs"></div>
|
||||
<button class="modal-x" data-close data-i18n-title="common.close" title="">
|
||||
<span data-ga-icon="x"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body modal-body-inset">
|
||||
<pre class="chan-log-pre" id="chan-log-pre"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- mykey 配置 弹窗 -->
|
||||
<div class="modal" id="chan-config-modal" hidden>
|
||||
<div class="modal-backdrop" data-close></div>
|
||||
<div class="modal-card modal-card-wide modal-card-inset" role="dialog">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" id="chan-config-title" data-i18n="modal.mykeyConfig"></div>
|
||||
<button class="modal-x" data-close data-i18n-title="common.close" title="">
|
||||
<span data-ga-icon="x"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body modal-body-inset chan-config-body">
|
||||
<textarea id="chan-config-editor" class="mykey-editor" spellcheck="false" maxlength="1000000"></textarea>
|
||||
<div class="chan-config-foot">
|
||||
<button type="button" class="chan-config-save" id="chan-config-save" data-i18n="common.save"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配置 弹窗 -->
|
||||
<div class="modal" id="settings-modal" hidden>
|
||||
<div class="modal-backdrop" data-close></div>
|
||||
<div class="modal-card" role="dialog">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" data-i18n="modal.settings"></div>
|
||||
<button class="modal-x" data-close data-i18n-title="common.close" title="">
|
||||
<span data-ga-icon="x"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body set-body">
|
||||
<div class="set-block">
|
||||
<div class="set-sec-t" data-i18n="set.appearance"></div>
|
||||
<div class="appear-cards" id="appearance-seg" role="radiogroup">
|
||||
<button class="appear-card sel" type="button" data-appearance="light" role="radio" aria-checked="true">
|
||||
<span class="appear-preview appear-preview-light" aria-hidden="true"></span>
|
||||
<span class="appear-name" data-i18n="appearance.light"></span>
|
||||
</button>
|
||||
<button class="appear-card" type="button" data-appearance="dark" role="radio" aria-checked="false">
|
||||
<span class="appear-preview appear-preview-dark" aria-hidden="true"></span>
|
||||
<span class="appear-name" data-i18n="appearance.dark"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="set-toggle-row" id="plain-ui-row" hidden>
|
||||
<span class="set-toggle-label" data-i18n="set.plainUi"></span>
|
||||
<button class="set-switch" type="button" id="plain-ui-switch" role="switch" aria-checked="false">
|
||||
<span class="set-switch-knob" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-block">
|
||||
<div class="set-sec-t" id="chat-font-label" data-i18n="set.fontSize"></div>
|
||||
<div class="chat-font-panel">
|
||||
<div class="chat-font-stepper" id="chat-font-stepper" role="slider"
|
||||
aria-valuemin="10" aria-valuemax="20" aria-valuenow="14"
|
||||
aria-valuetext="14px" aria-labelledby="chat-font-label" tabindex="0">
|
||||
<div class="chat-font-segments" id="chat-font-segments"></div>
|
||||
</div>
|
||||
<span class="chat-font-value" id="chat-font-value" aria-hidden="true">14px</span>
|
||||
<div class="chat-font-scale" aria-hidden="true">
|
||||
<span class="chat-font-scale-min">10</span>
|
||||
<span class="chat-font-scale-max">20</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-block">
|
||||
<div class="set-sec-t" data-i18n="set.lang"></div>
|
||||
<div class="model-list lang-list" id="lang-list"></div>
|
||||
</div>
|
||||
<div class="set-block">
|
||||
<div class="set-sec-t" data-i18n="set.model"></div>
|
||||
<div class="model-list" id="model-list"></div>
|
||||
<button class="set-btn" id="add-model-btn" type="button">
|
||||
<span data-ga-icon="plus"></span>
|
||||
<span data-i18n="set.addModel"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="set-block">
|
||||
<div class="set-sec-t" data-i18n="set.features"></div>
|
||||
<button class="set-btn" id="import-mykey-btn" type="button">
|
||||
<span data-ga-icon="fileText"></span>
|
||||
<span data-i18n="set.importMykey"></span>
|
||||
</button>
|
||||
<input type="file" id="import-mykey-input" accept=".py,text/plain" hidden>
|
||||
<button class="set-btn set-btn-follow" id="export-mykey-btn" type="button">
|
||||
<span data-ga-icon="folderSimple"></span>
|
||||
<span data-i18n="set.exportMykey"></span>
|
||||
</button>
|
||||
<button class="set-btn set-btn-follow" id="settings-services-btn" type="button">
|
||||
<span data-ga-icon="broadcast"></span>
|
||||
<span data-i18n="set.serviceManager"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片预览 lightbox -->
|
||||
<div class="lightbox" id="lightbox" hidden>
|
||||
<div class="lightbox-backdrop" data-close></div>
|
||||
<button class="lightbox-x" data-close data-i18n-title="lightbox.closeTitle" title="">×</button>
|
||||
<img class="lightbox-img" id="lightbox-img" alt="">
|
||||
</div>
|
||||
|
||||
<div id="toast-root" class="toast-root" aria-live="polite"></div>
|
||||
|
||||
<script src="vendor/marked.min.js"></script>
|
||||
<!-- KaTeX JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" crossorigin="anonymous"></script>
|
||||
<!-- highlight.js -->
|
||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js" crossorigin="anonymous"></script>
|
||||
<script src="phosphor-icons.js?v=2"></script>
|
||||
<script src="app.js?v=208"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>GenericAgent</title>
|
||||
<style>
|
||||
:root{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI","Microsoft YaHei UI","Helvetica Neue",Helvetica,Arial,sans-serif;background:#fff;color:#111}
|
||||
@media(prefers-color-scheme:dark){:root{background:#1a1a1a;color:#eee}}
|
||||
html,body{height:100%;margin:0;display:flex;align-items:center;justify-content:center}
|
||||
.card{text-align:center;opacity:.85;width:280px}
|
||||
.spinner{width:28px;height:28px;margin:0 auto 14px;border:3px solid #9994;border-top-color:currentColor;border-radius:50%;animation:spin 1s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
#ga-status{font-size:13px;line-height:1.5}
|
||||
.bar{display:none;width:240px;height:6px;margin:14px auto 0;background:#9993;border-radius:99px;overflow:hidden}
|
||||
.bar.show{display:block}
|
||||
#ga-bar{height:100%;width:0;background:currentColor;border-radius:99px;transition:width .3s ease}
|
||||
#ga-bar.indet{width:35%;animation:indet 1.1s ease-in-out infinite}
|
||||
@keyframes indet{0%{margin-left:-35%}100%{margin-left:100%}}
|
||||
</style>
|
||||
<script>
|
||||
// Loading window shows before the main UI, on a different origin than the app, so it can't
|
||||
// read the app's saved language; fall back to the system/browser locale.
|
||||
var GA_ZH = (navigator.language || '').toLowerCase().indexOf('zh') === 0;
|
||||
var GA_MSG = {
|
||||
starting_app: GA_ZH ? '正在启动 GenericAgent…' : 'Starting GenericAgent…',
|
||||
start: GA_ZH ? '首次启动,正在准备运行环境…' : 'First run: preparing the runtime…',
|
||||
venv: GA_ZH ? '正在创建运行环境…' : 'Creating the runtime environment…',
|
||||
deps: GA_ZH ? '正在安装依赖…' : 'Installing dependencies…',
|
||||
done: GA_ZH ? '依赖安装完成' : 'Dependencies installed',
|
||||
starting: GA_ZH ? '正在启动服务…' : 'Starting services…'
|
||||
};
|
||||
window.gaProgress = function(pct, key){
|
||||
var s = document.getElementById('ga-status');
|
||||
if (s) s.textContent = GA_MSG[key] || key;
|
||||
var bar = document.querySelector('.bar');
|
||||
var fill = document.getElementById('ga-bar');
|
||||
if (bar) bar.classList.add('show');
|
||||
if (fill){
|
||||
if (pct < 0){ fill.classList.add('indet'); }
|
||||
else { fill.classList.remove('indet'); fill.style.width = Math.max(0, Math.min(100, pct)) + '%'; }
|
||||
}
|
||||
};
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
var s = document.getElementById('ga-status');
|
||||
if (s) s.textContent = GA_MSG.starting_app;
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="spinner"></div>
|
||||
<div id="ga-status">正在启动 GenericAgent…</div>
|
||||
<div class="bar"><div id="ga-bar"></div></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,101 @@
|
||||
(() => {
|
||||
const PATHS = {
|
||||
chatTeardropText:
|
||||
'M172,112a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h68A8,8,0,0,1,172,112Zm-8,24H96a8,8,0,0,0,0,16h68a8,8,0,0,0,0-16Zm68-12A100.11,100.11,0,0,1,132,224H48a16,16,0,0,1-16-16V124a100,100,0,0,1,200,0Zm-16,0a84,84,0,0,0-168,0v84h84A84.09,84.09,0,0,0,216,124Z',
|
||||
broadcast:
|
||||
'M128,88a40,40,0,1,0,40,40A40,40,0,0,0,128,88Zm0,64a24,24,0,1,1,24-24A24,24,0,0,1,128,152Zm73.71,7.14a80,80,0,0,1-14.08,22.2,8,8,0,0,1-11.92-10.67,63.95,63.95,0,0,0,0-85.33,8,8,0,1,1,11.92-10.67,80.08,80.08,0,0,1,14.08,84.47ZM69,103.09a64,64,0,0,0,11.26,67.58,8,8,0,0,1-11.92,10.67,79.93,79.93,0,0,1,0-106.67A8,8,0,1,1,80.29,85.34,63.77,63.77,0,0,0,69,103.09ZM248,128a119.58,119.58,0,0,1-34.29,84,8,8,0,1,1-11.42-11.2,103.9,103.9,0,0,0,0-145.56A8,8,0,1,1,213.71,44,119.58,119.58,0,0,1,248,128ZM53.71,200.78A8,8,0,1,1,42.29,212a119.87,119.87,0,0,1,0-168,8,8,0,1,1,11.42,11.2,103.9,103.9,0,0,0,0,145.56Z',
|
||||
chartBar:
|
||||
'M224,200h-8V40a8,8,0,0,0-8-8H152a8,8,0,0,0-8,8V80H96a8,8,0,0,0-8,8v40H48a8,8,0,0,0-8,8v64H32a8,8,0,0,0,0,16H224a8,8,0,0,0,0-16ZM160,48h40V200H160ZM104,96h40V200H104ZM56,144H88v56H56Z',
|
||||
usersThree:
|
||||
'M244.8,150.4a8,8,0,0,1-11.2-1.6A51.6,51.6,0,0,0,192,128a8,8,0,0,1-7.37-4.89,8,8,0,0,1,0-6.22A8,8,0,0,1,192,112a24,24,0,1,0-23.24-30,8,8,0,1,1-15.5-4A40,40,0,1,1,219,117.51a67.94,67.94,0,0,1,27.43,21.68A8,8,0,0,1,244.8,150.4ZM190.92,212a8,8,0,1,1-13.84,8,57,57,0,0,0-98.16,0,8,8,0,1,1-13.84-8,72.06,72.06,0,0,1,33.74-29.92,48,48,0,1,1,58.36,0A72.06,72.06,0,0,1,190.92,212ZM128,176a32,32,0,1,0-32-32A32,32,0,0,0,128,176ZM72,120a8,8,0,0,0-8-8A24,24,0,1,1,87.24,82a8,8,0,1,0,15.5-4A40,40,0,1,0,37,117.51,67.94,67.94,0,0,0,9.6,139.19a8,8,0,1,0,12.8,9.61A51.6,51.6,0,0,1,64,128,8,8,0,0,0,72,120Z',
|
||||
coins:
|
||||
'M184,89.57V84c0-25.08-37.83-44-88-44S8,58.92,8,84v40c0,20.89,26.25,37.49,64,42.46V172c0,25.08,37.83,44,88,44s88-18.92,88-44V132C248,111.3,222.58,94.68,184,89.57ZM232,132c0,13.22-30.79,28-72,28-3.73,0-7.43-.13-11.08-.37C170.49,151.77,184,139,184,124V105.74C213.87,110.19,232,122.27,232,132ZM72,150.25V126.46A183.74,183.74,0,0,0,96,128a183.74,183.74,0,0,0,24-1.54v23.79A163,163,0,0,1,96,152,163,163,0,0,1,72,150.25Zm96-40.32V124c0,8.39-12.41,17.4-32,22.87V123.5C148.91,120.37,159.84,115.71,168,109.93ZM96,56c41.21,0,72,14.78,72,28s-30.79,28-72,28S24,97.22,24,84,54.79,56,96,56ZM24,124V109.93c8.16,5.78,19.09,10.44,32,13.57v23.37C36.41,141.4,24,132.39,24,124Zm64,48v-4.17c2.63.1,5.29.17,8,.17,3.88,0,7.67-.13,11.39-.35A121.92,121.92,0,0,0,120,171.41v23.46C100.41,189.4,88,180.39,88,172Zm48,26.25V174.4a179.48,179.48,0,0,0,24,1.6,183.74,183.74,0,0,0,24-1.54v23.79a165.45,165.45,0,0,1-48,0Zm64-3.38V171.5c12.91-3.13,23.84-7.79,32-13.57V172C232,180.39,219.59,189.4,200,194.87Z',
|
||||
sidebarSimple:
|
||||
'M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM40,56H80V200H40ZM216,200H96V56H216V200Z',
|
||||
pencilSimple:
|
||||
'M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z',
|
||||
magnifyingGlass:
|
||||
'M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z',
|
||||
books:
|
||||
'M231.65,194.55,198.46,36.75a16,16,0,0,0-19-12.39L132.65,34.42a16.08,16.08,0,0,0-12.3,19l33.19,157.8A16,16,0,0,0,169.16,224a16.25,16.25,0,0,0,3.38-.36l46.81-10.06A16.09,16.09,0,0,0,231.65,194.55ZM136,50.15c0-.06,0-.09,0-.09l46.8-10,3.33,15.87L139.33,66Zm6.62,31.47,46.82-10.05,3.34,15.9L146,97.53Zm6.64,31.57,46.82-10.06,13.3,63.24-46.82,10.06ZM216,197.94l-46.8,10-3.33-15.87L212.67,182,216,197.85C216,197.91,216,197.94,216,197.94ZM104,32H56A16,16,0,0,0,40,48V208a16,16,0,0,0,16,16h48a16,16,0,0,0,16-16V48A16,16,0,0,0,104,32ZM56,48h48V64H56Zm0,32h48v96H56Zm48,128H56V192h48v16Z',
|
||||
gridFour:
|
||||
'M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,80H136V56h64ZM120,56v64H56V56ZM56,136h64v64H56Zm144,64H136V136h64v64Z',
|
||||
robot:
|
||||
'M200,48H136V16a8,8,0,0,0-16,0V48H56A32,32,0,0,0,24,80V192a32,32,0,0,0,32,32H200a32,32,0,0,0,32-32V80A32,32,0,0,0,200,48Zm16,144a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V80A16,16,0,0,1,56,64H200a16,16,0,0,1,16,16Zm-52-56H92a28,28,0,0,0,0,56h72a28,28,0,0,0,0-56Zm-24,16v24H116V152ZM80,164a12,12,0,0,1,12-12h8v24H92A12,12,0,0,1,80,164Zm84,12h-8V152h8a12,12,0,0,1,0,24ZM72,108a12,12,0,1,1,12,12A12,12,0,0,1,72,108Zm88,0a12,12,0,1,1,12,12A12,12,0,0,1,160,108Z',
|
||||
dotsThree:
|
||||
'M140,128a12,12,0,1,1-12-12A12,12,0,0,1,140,128Zm56-12a12,12,0,1,0,12,12A12,12,0,0,0,196,116ZM60,116a12,12,0,1,0,12,12A12,12,0,0,0,60,116Z',
|
||||
folderSimplePlus:
|
||||
'M216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16H216.89A15.13,15.13,0,0,0,232,200.89V88A16,16,0,0,0,216,72Zm0,128H40V64H93.33L123.2,86.4A8,8,0,0,0,128,88h88Zm-56-56a8,8,0,0,1-8,8H136v16a8,8,0,0,1-16,0V152H104a8,8,0,0,1,0-16h16V120a8,8,0,0,1,16,0v16h16A8,8,0,0,1,160,144Z',
|
||||
folderSimple:
|
||||
'M216,72H130.67L102.93,51.2a16.12,16.12,0,0,0-9.6-3.2H40A16,16,0,0,0,24,64V200a16,16,0,0,0,16,16H216.89A15.13,15.13,0,0,0,232,200.89V88A16,16,0,0,0,216,72Zm0,128H40V64H93.33L123.2,86.4A8,8,0,0,0,128,88h88Z',
|
||||
bracketsCurly:
|
||||
'M43.18,128a29.78,29.78,0,0,1,8,10.26c4.8,9.9,4.8,22,4.8,33.74,0,24.31,1,36,24,36a8,8,0,0,1,0,16c-17.48,0-29.32-6.14-35.2-18.26-4.8-9.9-4.8-22-4.8-33.74,0-24.31-1-36-24-36a8,8,0,0,1,0-16c23,0,24-11.69,24-36,0-11.72,0-23.84,4.8-33.74C50.68,38.14,62.52,32,80,32a8,8,0,0,1,0,16C57,48,56,59.69,56,84c0,11.72,0,23.84-4.8,33.74A29.78,29.78,0,0,1,43.18,128ZM240,120c-23,0-24-11.69-24-36,0-11.72,0-23.84-4.8-33.74C205.32,38.14,193.48,32,176,32a8,8,0,0,0,0,16c23,0,24,11.69,24,36,0,11.72,0,23.84,4.8,33.74a29.78,29.78,0,0,0,8,10.26,29.78,29.78,0,0,0-8,10.26c-4.8,9.9-4.8,22-4.8,33.74,0,24.31-1,36-24,36a8,8,0,0,0,0,16c17.48,0,29.32-6.14,35.2-18.26,4.8-9.9,4.8-22,4.8-33.74,0-24.31,1-36,24-36a8,8,0,0,0,0-16Z',
|
||||
gear:
|
||||
'M128,80a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Zm88-29.84q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.21,107.21,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.71,107.71,0,0,0-26.25-10.87,8,8,0,0,0-7.06,1.49L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.21,107.21,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06Zm-16.1-6.5a73.93,73.93,0,0,1,0,8.68,8,8,0,0,0,1.74,5.48l14.19,17.73a91.57,91.57,0,0,1-6.23,15L187,173.11a8,8,0,0,0-5.1,2.64,74.11,74.11,0,0,1-6.14,6.14,8,8,0,0,0-2.64,5.1l-2.51,22.58a91.32,91.32,0,0,1-15,6.23l-17.74-14.19a8,8,0,0,0-5-1.75h-.48a73.93,73.93,0,0,1-8.68,0,8,8,0,0,0-5.48,1.74L100.45,215.8a91.57,91.57,0,0,1-15-6.23L82.89,187a8,8,0,0,0-2.64-5.1,74.11,74.11,0,0,1-6.14-6.14,8,8,0,0,0-5.1-2.64L46.43,170.6a91.32,91.32,0,0,1-6.23-15l14.19-17.74a8,8,0,0,0,1.74-5.48,73.93,73.93,0,0,1,0-8.68,8,8,0,0,0-1.74-5.48L40.2,100.45a91.57,91.57,0,0,1,6.23-15L69,82.89a8,8,0,0,0,5.1-2.64,74.11,74.11,0,0,1,6.14-6.14A8,8,0,0,0,82.89,69L85.4,46.43a91.32,91.32,0,0,1,15-6.23l17.74,14.19a8,8,0,0,0,5.48,1.74,73.93,73.93,0,0,1,8.68,0,8,8,0,0,0,5.48-1.74L155.55,40.2a91.57,91.57,0,0,1,15,6.23L173.11,69a8,8,0,0,0,2.64,5.1,74.11,74.11,0,0,1,6.14,6.14,8,8,0,0,0,5.1,2.64l22.58,2.51a91.32,91.32,0,0,1,6.23,15l-14.19,17.74A8,8,0,0,0,199.87,123.66Z',
|
||||
caretLeft:
|
||||
'M165.66,202.34a8,8,0,0,1-11.32,11.32l-80-80a8,8,0,0,1,0-11.32l80-80a8,8,0,0,1,11.32,11.32L91.31,128Z',
|
||||
caretDown:
|
||||
'M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z',
|
||||
caretRight:
|
||||
'M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z',
|
||||
paperPlaneTilt:
|
||||
'M227.32,28.68a16,16,0,0,0-15.66-4.08l-.15,0L19.57,82.84a16,16,0,0,0-2.49,29.8L102,154l41.3,84.87A15.86,15.86,0,0,0,157.74,248q.69,0,1.38-.06a15.88,15.88,0,0,0,14-11.51l58.2-191.94c0-.05,0-.1,0-.15A16,16,0,0,0,227.32,28.68ZM157.83,231.85l-.05.14,0-.07-40.06-82.3,48-48a8,8,0,0,0-11.31-11.31l-48,48L24.08,98.25l-.07,0,.14,0L216,40Z',
|
||||
paperclip:
|
||||
'M209.66,122.34a8,8,0,0,1,0,11.32l-82.05,82a56,56,0,0,1-79.2-79.21L147.67,35.73a40,40,0,1,1,56.61,56.55L105,193A24,24,0,1,1,71,159L154.3,74.38A8,8,0,1,1,165.7,85.6L82.39,170.31a8,8,0,1,0,11.27,11.36L192.93,81A24,24,0,1,0,159,47L59.76,147.68a40,40,0,1,0,56.53,56.62l82.06-82A8,8,0,0,1,209.66,122.34Z',
|
||||
lightning:
|
||||
'M215.79,118.17a8,8,0,0,0-5-5.66L153.18,90.9l14.66-73.33a8,8,0,0,0-13.69-7l-112,120a8,8,0,0,0,3,13l57.63,21.61L88.16,238.43a8,8,0,0,0,13.69,7l112-120A8,8,0,0,0,215.79,118.17ZM109.37,214l10.47-52.38a8,8,0,0,0-5-9.06L62,132.71l84.62-90.66L136.16,94.43a8,8,0,0,0,5,9.06l52.8,19.8Z',
|
||||
pushPinSimple:
|
||||
'M216,168h-9.29L185.54,48H192a8,8,0,0,0,0-16H64a8,8,0,0,0,0,16h6.46L49.29,168H40a8,8,0,0,0,0,16h80v56a8,8,0,0,0,16,0V184h80a8,8,0,0,0,0-16ZM86.71,48h82.58l21.17,120H65.54Z',
|
||||
trash:
|
||||
'M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z',
|
||||
x:
|
||||
'M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z',
|
||||
dotsThreeVertical:
|
||||
'M140,128a12,12,0,1,1-12-12A12,12,0,0,1,140,128Zm0-56a12,12,0,1,1-12-12A12,12,0,0,1,140,72Zm0,112a12,12,0,1,1-12-12A12,12,0,0,1,140,184Z',
|
||||
plus:
|
||||
'M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z',
|
||||
copy:
|
||||
'M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z',
|
||||
check:
|
||||
'M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z',
|
||||
crosshair:
|
||||
'M232,120h-8.34A96.14,96.14,0,0,0,136,32.34V24a8,8,0,0,0-16,0v8.34A96.14,96.14,0,0,0,32.34,120H24a8,8,0,0,0,0,16h8.34A96.14,96.14,0,0,0,120,223.66V232a8,8,0,0,0,16,0v-8.34A96.14,96.14,0,0,0,223.66,136H232a8,8,0,0,0,0-16Zm-96,87.6V200a8,8,0,0,0-16,0v7.6A80.15,80.15,0,0,1,48.4,136H56a8,8,0,0,0,0-16H48.4A80.15,80.15,0,0,1,120,48.4V56a8,8,0,0,0,16,0V48.4A80.15,80.15,0,0,1,207.6,120H200a8,8,0,0,0,0,16h7.6A80.15,80.15,0,0,1,136,207.6ZM128,88a40,40,0,1,0,40,40A40,40,0,0,0,128,88Zm0,64a24,24,0,1,1,24-24A24,24,0,0,1,128,152Z',
|
||||
compass:
|
||||
'M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216ZM172.42,72.84l-64,32a8.05,8.05,0,0,0-3.58,3.58l-32,64A8,8,0,0,0,80,184a8.1,8.1,0,0,0,3.58-.84l64-32a8.05,8.05,0,0,0,3.58-3.58l32-64a8,8,0,0,0-10.74-10.74ZM138,138,97.89,158.11,118,118l40.15-20.07Z',
|
||||
listChecks:
|
||||
'M224,128a8,8,0,0,1-8,8H128a8,8,0,0,1,0-16h88A8,8,0,0,1,224,128ZM128,72h88a8,8,0,0,0,0-16H128a8,8,0,0,0,0,16Zm88,112H128a8,8,0,0,0,0,16h88a8,8,0,0,0,0-16ZM82.34,42.34,56,68.69,45.66,58.34A8,8,0,0,0,34.34,69.66l16,16a8,8,0,0,0,11.32,0l32-32A8,8,0,0,0,82.34,42.34Zm0,64L56,132.69,45.66,122.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32Zm0,64L56,196.69,45.66,186.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32Z',
|
||||
star:
|
||||
'M239.18,97.26A16.38,16.38,0,0,0,224.92,86l-59-4.76L143.14,26.15a16.36,16.36,0,0,0-30.27,0L90.11,81.23,31.08,86a16.46,16.46,0,0,0-9.37,28.86l45,38.83L53,211.75a16.38,16.38,0,0,0,24.5,17.82L128,198.49l50.53,31.08A16.4,16.4,0,0,0,203,211.75l-13.76-58.07,45-38.83A16.43,16.43,0,0,0,239.18,97.26Zm-15.34,5.47-48.7,42a8,8,0,0,0-2.56,7.91l14.88,62.8a.37.37,0,0,1-.17.48c-.18.14-.23.11-.38,0l-54.72-33.65a8,8,0,0,0-8.38,0L69.09,215.94c-.15.09-.19.12-.38,0a.37.37,0,0,1-.17-.48l14.88-62.8a8,8,0,0,0-2.56-7.91l-48.7-42c-.12-.1-.23-.19-.13-.5s.18-.27.33-.29l63.92-5.16A8,8,0,0,0,103,91.86l24.62-59.61c.08-.17.11-.25.35-.25s.27.08.35.25L153,91.86a8,8,0,0,0,6.75,4.92l63.92,5.16c.15,0,.24,0,.33.29S224,102.63,223.84,102.73Z',
|
||||
fileText:
|
||||
'M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Zm-32-80a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,136Zm0,32a8,8,0,0,1-8,8H96a8,8,0,0,1,0-16h64A8,8,0,0,1,168,168Z',
|
||||
gitFork:
|
||||
'M224,64a32,32,0,1,0-40,31v17a8,8,0,0,1-8,8H80a8,8,0,0,1-8-8V95a32,32,0,1,0-16,0v17a24,24,0,0,0,24,24h40v25a32,32,0,1,0,16,0V136h40a24,24,0,0,0,24-24V95A32.06,32.06,0,0,0,224,64ZM48,64A16,16,0,1,1,64,80,16,16,0,0,1,48,64Zm96,128a16,16,0,1,1-16-16A16,16,0,0,1,144,192ZM192,80a16,16,0,1,1,16-16A16,16,0,0,1,192,80Z',
|
||||
hexagon:
|
||||
'M223.68,66.15,135.68,18h0a15.88,15.88,0,0,0-15.36,0l-88,48.17a16,16,0,0,0-8.32,14v95.64a16,16,0,0,0,8.32,14l88,48.17a15.88,15.88,0,0,0,15.36,0l88-48.17a16,16,0,0,0,8.32-14V80.18A16,16,0,0,0,223.68,66.15ZM216,175.82,128,224,40,175.82V80.18L128,32h0l88,48.17Z',
|
||||
};
|
||||
|
||||
function gaIcon(name, className = '') {
|
||||
const d = PATHS[name];
|
||||
if (!d) return '';
|
||||
const cls = className ? ` class="${className}"` : '';
|
||||
return `<svg${cls} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true" focusable="false"><path d="${d}"/></svg>`;
|
||||
}
|
||||
|
||||
function gaHydrateIcons(root = document) {
|
||||
root.querySelectorAll('[data-ga-icon]').forEach((node) => {
|
||||
node.outerHTML = gaIcon(node.dataset.gaIcon, node.className || '');
|
||||
});
|
||||
}
|
||||
|
||||
window.GA_ICON_PATHS = PATHS;
|
||||
window.gaIcon = gaIcon;
|
||||
window.gaHydrateIcons = gaHydrateIcons;
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
const hydrate = () => gaHydrateIcons(document);
|
||||
hydrate();
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', hydrate, { once: true });
|
||||
}
|
||||
}
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user