llm-api-gateway-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -0
- package/README.md +1127 -0
- package/cli-agent.js +666 -0
- package/cli-anthropic.js +236 -0
- package/cli-claude-code.js +317 -0
- package/cli-openai.js +212 -0
- package/completions/_llm-api-gateway-cli +65 -0
- package/completions/llm-api-gateway-cli.bash +64 -0
- package/completions/llm-api-gateway-cli.fish +43 -0
- package/images/chat.png +0 -0
- package/images/settings.png +0 -0
- package/images/task.png +0 -0
- package/lib/agent.js +607 -0
- package/lib/commands.js +468 -0
- package/lib/common.js +196 -0
- package/lib/config.js +70 -0
- package/lib/configcmd.js +230 -0
- package/lib/hub.js +1494 -0
- package/lib/jsonstore.js +49 -0
- package/lib/mcp.js +375 -0
- package/lib/memory.js +109 -0
- package/lib/plandoc.js +178 -0
- package/lib/pricing.js +52 -0
- package/lib/runner.js +234 -0
- package/lib/runstore.js +96 -0
- package/lib/secrets.js +198 -0
- package/lib/sessionstore.js +269 -0
- package/lib/settings.js +517 -0
- package/lib/tasksession.js +594 -0
- package/lib/taskstore.js +740 -0
- package/lib/tools.js +927 -0
- package/package.json +55 -0
- package/public/app.js +1055 -0
- package/public/index.html +167 -0
- package/public/manual.css +215 -0
- package/public/manual.html +381 -0
- package/public/manual.js +186 -0
- package/public/models.js +121 -0
- package/public/render.js +250 -0
- package/public/styles.css +955 -0
- package/public/task-slash.js +493 -0
- package/public/task.css +739 -0
- package/public/task.html +220 -0
- package/public/task.js +3127 -0
- package/public/theme.js +91 -0
- package/public/tint.js +261 -0
- package/scripts/install.ps1 +537 -0
- package/scripts/install.sh +510 -0
- package/server.js +14 -0
- package/task-server.js +15 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>LLM API Gateway · 聊天</title>
|
|
7
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
8
|
+
<script>
|
|
9
|
+
// 防闪白:必须在首次绘制前把主题定下来,所以内联在这里(逻辑与 /theme.js 一致)
|
|
10
|
+
(() => {
|
|
11
|
+
try {
|
|
12
|
+
let t = localStorage.getItem('lgw.theme');
|
|
13
|
+
if (t !== 'dark' && t !== 'light') {
|
|
14
|
+
t = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
15
|
+
}
|
|
16
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
17
|
+
document.documentElement.style.colorScheme = t;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
</script>
|
|
23
|
+
<!-- 外部集成:地址上带 ?bg=<底色> 时按底色推导整套配色。必须是阻塞脚本(排在 theme.js 之前),
|
|
24
|
+
否则会先按默认配色画一帧再变色 —— 这就是所谓的闪一下 -->
|
|
25
|
+
<script src="/tint.js"></script>
|
|
26
|
+
<script src="/theme.js" defer></script>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<div class="app">
|
|
30
|
+
<aside class="sidebar" id="sidebar">
|
|
31
|
+
<div class="sidebar-head">
|
|
32
|
+
<a class="btn block task-entry" id="btn-open-task" href="/task" target="_blank" rel="noopener"
|
|
33
|
+
title="进入任务模式:选一个本地目录,让模型真读写其中的文件">
|
|
34
|
+
<span>+ 新任务</span>
|
|
35
|
+
<span class="ext-mark" aria-hidden="true">↗</span>
|
|
36
|
+
</a>
|
|
37
|
+
<button class="btn primary block" id="btn-new-session" type="button">+ 新对话</button>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="session-list" id="session-list"></div>
|
|
40
|
+
<div class="sidebar-foot">
|
|
41
|
+
<span id="sessions-count">会话 0 / 20</span>
|
|
42
|
+
<span class="foot-sep">·</span>
|
|
43
|
+
<span id="sessions-retention">滑动保留 15 天</span>
|
|
44
|
+
<button class="btn ghost tiny" id="btn-prune" type="button" title="立即清理已过期或超量的会话">清理</button>
|
|
45
|
+
</div>
|
|
46
|
+
</aside>
|
|
47
|
+
|
|
48
|
+
<main class="main">
|
|
49
|
+
<header class="topbar">
|
|
50
|
+
<button class="btn ghost icon-only" id="btn-toggle-sidebar" type="button" title="会话列表">☰</button>
|
|
51
|
+
<div class="brand">
|
|
52
|
+
<span class="dot" id="status-dot"></span>
|
|
53
|
+
<h1>LLM API Gateway</h1>
|
|
54
|
+
<span class="tag">Web UI</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="meta" id="gateway-meta">加载中…</div>
|
|
57
|
+
<div class="topbar-actions">
|
|
58
|
+
<a class="btn ghost" id="btn-open-manual" href="/manual" target="_blank" rel="noopener"
|
|
59
|
+
title="操作手册:任务页操作流程 + 三端斜杠指令对照">手册</a>
|
|
60
|
+
<div class="theme-switch" role="group" aria-label="主题">
|
|
61
|
+
<button class="theme-btn" type="button" data-theme-option="system" title="跟随系统" aria-label="跟随系统">◐</button>
|
|
62
|
+
<button class="theme-btn" type="button" data-theme-option="light" title="白天" aria-label="白天">☀</button>
|
|
63
|
+
<button class="theme-btn" type="button" data-theme-option="dark" title="黑夜" aria-label="黑夜">☾</button>
|
|
64
|
+
</div>
|
|
65
|
+
<button class="btn ghost" id="btn-settings" type="button">设置</button>
|
|
66
|
+
<button class="btn ghost" id="btn-clear" type="button">新对话</button>
|
|
67
|
+
</div>
|
|
68
|
+
</header>
|
|
69
|
+
|
|
70
|
+
<section class="messages" id="messages">
|
|
71
|
+
<div class="empty" id="empty-hint">
|
|
72
|
+
<h2>开始一段对话</h2>
|
|
73
|
+
<p>请求经本机 <code>server.js</code> 转发到网关的 <code>/v1/chat/completions</code>,密钥只留在服务端。</p>
|
|
74
|
+
<div class="chips">
|
|
75
|
+
<button class="chip" data-prompt="你好,用一句话介绍你自己" type="button">你好,用一句话介绍你自己</button>
|
|
76
|
+
<button class="chip" data-prompt="用 Node.js 写一个带超时的 fetch 封装,并解释关键点" type="button">写一段带超时的 fetch 封装</button>
|
|
77
|
+
<button class="chip" data-prompt="解释一下 SSE 流式输出在前端是怎么消费的" type="button">解释 SSE 流式消费</button>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
|
|
82
|
+
<footer class="composer">
|
|
83
|
+
<div class="composer-box">
|
|
84
|
+
<textarea id="input" rows="1" placeholder="输入消息,Enter 发送,Shift+Enter 换行" autocomplete="off"></textarea>
|
|
85
|
+
<div class="composer-bar">
|
|
86
|
+
<span class="hint" id="status">就绪</span>
|
|
87
|
+
<div class="composer-buttons">
|
|
88
|
+
<button class="btn" id="btn-stop" type="button" hidden>停止</button>
|
|
89
|
+
<button class="btn primary" id="btn-send" type="button">发送</button>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</footer>
|
|
94
|
+
</main>
|
|
95
|
+
|
|
96
|
+
<aside class="settings" id="settings">
|
|
97
|
+
<div class="settings-head">
|
|
98
|
+
<h2>设置</h2>
|
|
99
|
+
<button class="btn ghost" id="btn-settings-close" type="button">关闭</button>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<label class="field">
|
|
103
|
+
<span>模型</span>
|
|
104
|
+
<select id="set-model" title="只能从网关拉到的模型列表里选;列表不对就点「刷新模型」"></select>
|
|
105
|
+
<small id="models-hint">模型列表由网关 /v1/models 提供,只能选、不能填</small>
|
|
106
|
+
</label>
|
|
107
|
+
|
|
108
|
+
<label class="field">
|
|
109
|
+
<span>系统提示词</span>
|
|
110
|
+
<textarea id="set-system" rows="4" placeholder="留空则不发送 system 消息"></textarea>
|
|
111
|
+
</label>
|
|
112
|
+
|
|
113
|
+
<label class="field">
|
|
114
|
+
<span>网关密钥</span>
|
|
115
|
+
<input id="set-key" type="password" autocomplete="off" spellcheck="false"
|
|
116
|
+
placeholder="未设置:粘贴 sk-xxx 后点「保存密钥」" />
|
|
117
|
+
<small id="key-hint">存到 credentials.json(仅本用户可读),不写进 config.json;环境变量 / .env / --key 给的密钥优先于它。留空 = 不改动。</small>
|
|
118
|
+
</label>
|
|
119
|
+
|
|
120
|
+
<div class="row">
|
|
121
|
+
<label class="field">
|
|
122
|
+
<span>温度</span>
|
|
123
|
+
<input id="set-temperature" type="number" min="0" max="2" step="0.1" />
|
|
124
|
+
</label>
|
|
125
|
+
<label class="field">
|
|
126
|
+
<span>最大 tokens</span>
|
|
127
|
+
<input id="set-max-tokens" type="number" min="0" step="64" placeholder="0 = 不限制" />
|
|
128
|
+
</label>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<label class="check">
|
|
132
|
+
<input id="set-stream" type="checkbox" />
|
|
133
|
+
<span>流式输出</span>
|
|
134
|
+
</label>
|
|
135
|
+
|
|
136
|
+
<label class="check" title="思考模型(如 deepseek-v4-*)会先输出思维链;关闭后只显示正文">
|
|
137
|
+
<input id="set-show-reasoning" type="checkbox" />
|
|
138
|
+
<span>显示思考过程</span>
|
|
139
|
+
</label>
|
|
140
|
+
|
|
141
|
+
<label class="field">
|
|
142
|
+
<span>上下文上限(字符)</span>
|
|
143
|
+
<input id="set-history-chars" type="number" min="0" step="1000" placeholder="0 = 不限,全量发送" />
|
|
144
|
+
<small id="history-hint">超出后只发送最近的消息,避免请求体无限增长</small>
|
|
145
|
+
</label>
|
|
146
|
+
|
|
147
|
+
<div class="settings-actions">
|
|
148
|
+
<button class="btn ghost" id="btn-save-key" type="button">保存密钥</button>
|
|
149
|
+
<button class="btn ghost" id="btn-refresh-models" type="button">刷新模型</button>
|
|
150
|
+
<button class="btn ghost" id="btn-reset" type="button">恢复默认</button>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="info">
|
|
154
|
+
<div class="info-row"><span>网关地址</span><code id="info-base">—</code></div>
|
|
155
|
+
<div class="info-row"><span>密钥</span><code id="info-key">—</code></div>
|
|
156
|
+
<div class="info-row"><span>累计用量</span><code id="info-usage">—</code></div>
|
|
157
|
+
<div class="info-row"><span>本地会话</span><code id="info-sessions">—</code></div>
|
|
158
|
+
<div class="info-row"><span>保留策略</span><code id="info-retention">—</code></div>
|
|
159
|
+
</div>
|
|
160
|
+
</aside>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<script src="/render.js"></script>
|
|
164
|
+
<script src="/models.js"></script>
|
|
165
|
+
<script src="/app.js"></script>
|
|
166
|
+
</body>
|
|
167
|
+
</html>
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/* 操作手册页(/manual)
|
|
2
|
+
*
|
|
3
|
+
* 只做排版:配色、按钮、主题全部复用 styles.css 的变量与类(--bg / --border / --text / .btn ...),
|
|
4
|
+
* 这样白天/黑夜、外部底色(?bg=)三种情况下都与另外两个页面一致,不用第二套配色。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
.manual-topbar {
|
|
8
|
+
position: sticky;
|
|
9
|
+
top: 0;
|
|
10
|
+
z-index: 5;
|
|
11
|
+
background: var(--bg-soft);
|
|
12
|
+
border-bottom: 1px solid var(--border);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.manual-main {
|
|
16
|
+
max-width: 1000px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
padding: 28px 20px 90px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.manual-head h1 {
|
|
22
|
+
font-size: 26px;
|
|
23
|
+
margin: 0 0 10px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.manual-lede {
|
|
27
|
+
color: var(--text-dim);
|
|
28
|
+
line-height: 1.75;
|
|
29
|
+
margin: 0 0 12px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.manual-counts {
|
|
33
|
+
font-family: var(--mono);
|
|
34
|
+
font-size: 12px;
|
|
35
|
+
color: var(--text-faint);
|
|
36
|
+
margin: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* 报错行常驻占位:读不到数据时文案不会把下面的内容顶下去 */
|
|
40
|
+
.manual-status {
|
|
41
|
+
min-height: 18px;
|
|
42
|
+
margin: 8px 0 0;
|
|
43
|
+
font-size: 13px;
|
|
44
|
+
color: var(--danger);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.manual-toc {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
gap: 8px;
|
|
51
|
+
margin: 20px 0 4px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.manual-toc a {
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
color: var(--text-dim);
|
|
57
|
+
text-decoration: none;
|
|
58
|
+
border: 1px solid var(--border);
|
|
59
|
+
border-radius: 999px;
|
|
60
|
+
padding: 4px 10px;
|
|
61
|
+
transition: color var(--t), border-color var(--t);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.manual-toc a:hover {
|
|
65
|
+
color: var(--text);
|
|
66
|
+
border-color: var(--border-strong);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.manual-main section {
|
|
70
|
+
margin-top: 34px;
|
|
71
|
+
scroll-margin-top: 70px; /* 顶栏是 sticky 的,锚点跳过来别被盖住 */
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.manual-main h2 {
|
|
75
|
+
font-size: 18px;
|
|
76
|
+
margin: 0 0 12px;
|
|
77
|
+
padding-bottom: 6px;
|
|
78
|
+
border-bottom: 1px solid var(--border);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.manual-main h3 {
|
|
82
|
+
font-size: 15px;
|
|
83
|
+
margin: 24px 0 6px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.manual-main p,
|
|
87
|
+
.manual-main li,
|
|
88
|
+
.manual-main dd {
|
|
89
|
+
line-height: 1.8;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.manual-main ul,
|
|
93
|
+
.manual-main ol {
|
|
94
|
+
padding-left: 22px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.manual-main li + li {
|
|
98
|
+
margin-top: 4px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.manual-main a {
|
|
102
|
+
color: var(--accent);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.manual-main code {
|
|
106
|
+
font-family: var(--mono);
|
|
107
|
+
font-size: 12.5px;
|
|
108
|
+
background: var(--bg-code);
|
|
109
|
+
border: 1px solid var(--border);
|
|
110
|
+
border-radius: 6px;
|
|
111
|
+
padding: 1px 5px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.manual-note {
|
|
115
|
+
color: var(--text-dim);
|
|
116
|
+
font-size: 13px;
|
|
117
|
+
line-height: 1.7;
|
|
118
|
+
margin: 0 0 10px;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* 表格:窄屏靠外层横向滚动,不把页面撑破 */
|
|
122
|
+
.manual-table-wrap {
|
|
123
|
+
overflow-x: auto;
|
|
124
|
+
border: 1px solid var(--border);
|
|
125
|
+
border-radius: var(--radius);
|
|
126
|
+
margin-bottom: 6px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
table.manual-table {
|
|
130
|
+
border-collapse: collapse;
|
|
131
|
+
width: 100%;
|
|
132
|
+
font-size: 13px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
table.manual-table th,
|
|
136
|
+
table.manual-table td {
|
|
137
|
+
text-align: left;
|
|
138
|
+
padding: 8px 12px;
|
|
139
|
+
border-bottom: 1px solid var(--border);
|
|
140
|
+
vertical-align: top;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
table.manual-table th {
|
|
144
|
+
background: var(--bg-raised);
|
|
145
|
+
color: var(--text-dim);
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
white-space: nowrap;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
table.manual-table tbody tr:last-child td {
|
|
151
|
+
border-bottom: 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
table.manual-table td.cmd {
|
|
155
|
+
font-family: var(--mono);
|
|
156
|
+
white-space: nowrap;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
table.manual-table td.cap {
|
|
160
|
+
font-family: var(--mono);
|
|
161
|
+
color: var(--text-dim);
|
|
162
|
+
white-space: nowrap;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* 三端对照表里的 ✓ / — */
|
|
166
|
+
table.manual-table td.yes {
|
|
167
|
+
color: var(--ok);
|
|
168
|
+
text-align: center;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
table.manual-table td.no {
|
|
172
|
+
color: var(--text-faint);
|
|
173
|
+
text-align: center;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* 代码块:复用 styles.css 的 .code-block,但把 .manual-main code 的行内样式压回去
|
|
177
|
+
* (两份样式表里的选择器权重相同,manual.css 在后会赢,不给一条更具体的选择器就变成行内小块)。 */
|
|
178
|
+
.manual-main .code-block {
|
|
179
|
+
margin: 0 0 12px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.manual-main .code-block code {
|
|
183
|
+
display: block;
|
|
184
|
+
padding: 12px 14px;
|
|
185
|
+
background: none;
|
|
186
|
+
border: none;
|
|
187
|
+
border-radius: 0;
|
|
188
|
+
font-size: 12.5px;
|
|
189
|
+
line-height: 1.6;
|
|
190
|
+
white-space: pre;
|
|
191
|
+
color: var(--text);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.manual-main section > h3:first-of-type {
|
|
195
|
+
margin-top: 18px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.manual-faq dt {
|
|
199
|
+
font-weight: 600;
|
|
200
|
+
margin-top: 16px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.manual-faq dd {
|
|
204
|
+
margin: 4px 0 0;
|
|
205
|
+
color: var(--text-dim);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.manual-foot {
|
|
209
|
+
margin-top: 40px;
|
|
210
|
+
padding-top: 14px;
|
|
211
|
+
border-top: 1px solid var(--border);
|
|
212
|
+
color: var(--text-faint);
|
|
213
|
+
font-size: 12.5px;
|
|
214
|
+
line-height: 1.8;
|
|
215
|
+
}
|