claude-simple 0.2.4 → 0.2.6
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/README.md +1 -0
- package/bin/edge-ctl.js +282 -0
- package/dist/{app.0.2.4.min.js → app.0.2.6.min.js} +131 -115
- package/dist/index.html +2 -2
- package/dist/{style.0.2.4.min.css → style.0.2.6.min.css} +194 -58
- package/package.json +6 -2
- package/public/app.js +12 -2
- package/public/chat.js +74 -15
- package/public/index.html +33 -3
- package/public/mobile.js +72 -0
- package/public/pwa.js +32 -2
- package/public/shell/boot.js +2 -0
- package/public/shell/hub.js +5 -1
- package/public/shell/layout.js +11 -2
- package/public/shell/templates.js +36 -21
- package/public/shell.js +34 -4
- package/public/state.js +8 -1
- package/public/style.css +193 -57
- package/public/sw.js +2 -2
- package/src/app.js +21 -7
package/public/shell.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from './state.js';
|
|
15
15
|
import { api } from './api.js';
|
|
16
16
|
import { promptPwaInstall } from './pwa.js';
|
|
17
|
+
import { syncFocusScope } from './mobile.js';
|
|
17
18
|
import { getLastSessionContext } from './shell/session-context.js';
|
|
18
19
|
import {
|
|
19
20
|
connectWS, clearMessages, appendMsg, appendSystemMsg, sendMessage, stopProcessing,
|
|
@@ -66,7 +67,8 @@ function showPwaInstallHelp() {
|
|
|
66
67
|
<li>下滑找到并点 <strong>添加到主屏幕</strong></li>
|
|
67
68
|
<li>确认名称后点 <strong>添加</strong></li>
|
|
68
69
|
</ol>
|
|
69
|
-
<p class="text-xs text-base-content/50 mt-3">需用 <strong>Safari</strong> 打开本站;微信/Chrome 内置浏览器通常没有「添加到主屏幕」。</p
|
|
70
|
+
<p class="text-xs text-base-content/50 mt-3">需用 <strong>Safari</strong> 打开本站;微信/Chrome 内置浏览器通常没有「添加到主屏幕」。</p>
|
|
71
|
+
<p class="text-xs text-warning/80 mt-2">当前若是 IP + 自签 HTTPS,Safari 仍会显示「不安全」。加到主屏幕可去掉浏览器外壳,但不能去掉系统输入条。彻底干净需要正式域名证书。</p>`;
|
|
70
72
|
} else if (isAndroid) {
|
|
71
73
|
body = `<ol class="list-decimal pl-4 space-y-2 text-sm text-base-content/80">
|
|
72
74
|
<li>点右上角菜单 <strong>⋮</strong></li>
|
|
@@ -107,8 +109,30 @@ export async function initAuth() {
|
|
|
107
109
|
app.classList.add('hidden');
|
|
108
110
|
app.style.display = 'none';
|
|
109
111
|
}
|
|
110
|
-
const status = await api('GET', '/api/auth/status');
|
|
111
112
|
const btn = $('auth-btn');
|
|
113
|
+
let status;
|
|
114
|
+
const controller = new AbortController();
|
|
115
|
+
const timer = setTimeout(() => controller.abort(), 8000);
|
|
116
|
+
try {
|
|
117
|
+
// This is the first request made by a cold PWA launch. Bound it so a
|
|
118
|
+
// captive portal, stale reverse proxy, or sleeping hub cannot strand the
|
|
119
|
+
// app behind its boot screen indefinitely.
|
|
120
|
+
status = await api('GET', '/api/auth/status', undefined, controller.signal);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.warn('[auth status]', e);
|
|
123
|
+
const error = $('auth-error');
|
|
124
|
+
if (error) {
|
|
125
|
+
error.textContent = '无法连接服务器。请检查网络或服务地址后重试。';
|
|
126
|
+
error.classList.remove('hidden');
|
|
127
|
+
}
|
|
128
|
+
// The normal sign-in form remains usable: a transient status failure
|
|
129
|
+
// should be visible, not turn the installed app into a black screen.
|
|
130
|
+
if (btn) btn.dataset.mode = 'login';
|
|
131
|
+
return;
|
|
132
|
+
} finally {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
}
|
|
135
|
+
|
|
112
136
|
if (status.needsSetup) {
|
|
113
137
|
$('auth-subtitle').textContent = 'Create your account to get started';
|
|
114
138
|
btn.textContent = 'Create Account';
|
|
@@ -276,6 +300,7 @@ export function initShell(opts = {}) {
|
|
|
276
300
|
document.dispatchEvent(new CustomEvent('shell-tab-shown'));
|
|
277
301
|
});
|
|
278
302
|
}
|
|
303
|
+
syncFocusScope();
|
|
279
304
|
});
|
|
280
305
|
|
|
281
306
|
initSidebarResize();
|
|
@@ -307,6 +332,7 @@ export function initShell(opts = {}) {
|
|
|
307
332
|
|
|
308
333
|
delegate.on('click', '#btn-home', goHome);
|
|
309
334
|
delegate.on('click', '#auth-btn', submitAuth);
|
|
335
|
+
delegate.on('submit', '#auth-form', e => { e.preventDefault(); submitAuth(); });
|
|
310
336
|
delegate.on('keydown', '#auth-username, #auth-password', e => { if (e.key === 'Enter') submitAuth(); });
|
|
311
337
|
delegate.on('click', '#hamburger', () => $('sidebar').classList.contains('open') ? closeSidebar() : openSidebar());
|
|
312
338
|
delegate.on('click', '#sidebar-overlay', closeSidebar);
|
|
@@ -460,7 +486,10 @@ export function initShell(opts = {}) {
|
|
|
460
486
|
delegate.on('click', '#git-root-btn', () => applyRoot('git-root-input', gitRoot));
|
|
461
487
|
delegate.on('keydown', '#files-root-input', e => { if (e.key === 'Enter') applyRoot('files-root-input', filesRoot); });
|
|
462
488
|
delegate.on('keydown', '#git-root-input', e => { if (e.key === 'Enter') applyRoot('git-root-input', gitRoot); });
|
|
463
|
-
delegate.on('click', '#btn-opts', () => {
|
|
489
|
+
delegate.on('click', '#btn-opts', () => {
|
|
490
|
+
$('chat-opts').classList.toggle('hidden');
|
|
491
|
+
syncFocusScope();
|
|
492
|
+
});
|
|
464
493
|
|
|
465
494
|
delegate.on('change', '#sel-agent', (_, el) => {
|
|
466
495
|
const prev = currentAgent.peek();
|
|
@@ -470,6 +499,7 @@ export function initShell(opts = {}) {
|
|
|
470
499
|
setAgent(next);
|
|
471
500
|
currentModel.value = getDefaultModel(next) || currentModel.peek();
|
|
472
501
|
localStorage.setItem('model', currentModel.peek());
|
|
502
|
+
$('sel-permission').value = currentPermission.peek();
|
|
473
503
|
refreshModelSelect();
|
|
474
504
|
appendSystemMsg(`Agent → ${AGENT_LABELS[next] || next} · model ${currentModel.peek()} · new session`);
|
|
475
505
|
}
|
|
@@ -700,6 +730,7 @@ export function initShell(opts = {}) {
|
|
|
700
730
|
});
|
|
701
731
|
|
|
702
732
|
refreshModelSelect();
|
|
733
|
+
$('sel-permission').value = currentPermission.peek();
|
|
703
734
|
applyChatDensity();
|
|
704
735
|
const dens = $('sel-density');
|
|
705
736
|
if (dens) dens.value = chatDensity.peek() || 'normal';
|
|
@@ -718,4 +749,3 @@ export function initShell(opts = {}) {
|
|
|
718
749
|
btn.classList.toggle('text-base-content/50', !on);
|
|
719
750
|
});
|
|
720
751
|
}
|
|
721
|
-
|
package/public/state.js
CHANGED
|
@@ -18,7 +18,11 @@ export const wsStatus = signal('idle');
|
|
|
18
18
|
export const currentAgent = signal(localStorage.getItem('agent') || 'claude'); // 'claude'|'codex'|'grok'
|
|
19
19
|
export const currentModel = signal(localStorage.getItem('model') || 'claude-sonnet-4-5');
|
|
20
20
|
export const currentEffort = signal('');
|
|
21
|
-
|
|
21
|
+
// Codex is intentionally trusted by default; users can still choose another
|
|
22
|
+
// mode from the expanded composer options before starting a turn.
|
|
23
|
+
export const currentPermission = signal(
|
|
24
|
+
currentAgent.peek() === 'codex' ? 'bypassPermissions' : 'default'
|
|
25
|
+
); // 'default'|'acceptEdits'|'bypassPermissions'|'plan'|'auto'
|
|
22
26
|
export const filesRoot = signal(''); // custom root override for Files tab
|
|
23
27
|
export const gitRoot = signal(''); // custom root override for Git tab
|
|
24
28
|
export const agentsMeta = signal(null); // from /api/agents (dynamic model lists)
|
|
@@ -247,6 +251,9 @@ export function setAgent(agent) {
|
|
|
247
251
|
const a = (agent || 'claude').toLowerCase();
|
|
248
252
|
currentAgent.value = a;
|
|
249
253
|
localStorage.setItem('agent', a);
|
|
254
|
+
currentPermission.value = a === 'codex' ? 'bypassPermissions' : 'default';
|
|
255
|
+
const permissionSelect = typeof document === 'undefined' ? null : document.getElementById('sel-permission');
|
|
256
|
+
if (permissionSelect) permissionSelect.value = currentPermission.peek();
|
|
250
257
|
const models = getModelsForAgent(a);
|
|
251
258
|
const def = getDefaultModel(a);
|
|
252
259
|
if (!models.some(m => m.value === currentModel.peek())) {
|
package/public/style.css
CHANGED
|
@@ -13,29 +13,21 @@
|
|
|
13
13
|
#sidebar.open { transform: translateX(0); box-shadow: 4px 0 20px rgba(0,0,0,.4); }
|
|
14
14
|
|
|
15
15
|
/* Compact chat composer on phones — less height, less dead space under bar */
|
|
16
|
-
#composer-row {
|
|
17
|
-
|
|
18
|
-
padding: 0.4rem 0.55rem 0.35rem !important;
|
|
19
|
-
}
|
|
16
|
+
#composer-row { padding: 0 !important; }
|
|
17
|
+
.composer-surface { min-height: 0; border-radius: 1.35rem; }
|
|
20
18
|
#chat-input.chat-input,
|
|
21
19
|
#chat-input {
|
|
22
|
-
min-height:
|
|
23
|
-
max-height:
|
|
24
|
-
padding: 0
|
|
25
|
-
border-radius: 0
|
|
20
|
+
min-height: 32px !important;
|
|
21
|
+
max-height: 72px !important;
|
|
22
|
+
padding: 0 !important;
|
|
23
|
+
border-radius: 1.15rem 1.15rem 0 0;
|
|
26
24
|
/* 16px prevents iOS Safari auto-zoom on focus */
|
|
27
25
|
font-size: 16px !important;
|
|
28
|
-
line-height: 1.
|
|
29
|
-
}
|
|
30
|
-
#btn-opts,
|
|
31
|
-
#btn-attach,
|
|
32
|
-
#send-btn,
|
|
33
|
-
#stop-btn {
|
|
34
|
-
height: 36px !important;
|
|
35
|
-
min-height: 36px !important;
|
|
36
|
-
padding-left: 0.65rem;
|
|
37
|
-
padding-right: 0.65rem;
|
|
26
|
+
line-height: 1.4 !important;
|
|
38
27
|
}
|
|
28
|
+
.composer-icon-btn { width: 40px; height: 40px; font-size: 19px; }
|
|
29
|
+
.composer-send-btn, .composer-stop-btn { min-height: 40px; height: 40px; }
|
|
30
|
+
.composer-hint { display: none; }
|
|
39
31
|
#chat-opts {
|
|
40
32
|
padding-top: 0.35rem !important;
|
|
41
33
|
padding-left: 0.55rem !important;
|
|
@@ -49,6 +41,64 @@
|
|
|
49
41
|
.file-row-action {
|
|
50
42
|
opacity: 1 !important;
|
|
51
43
|
}
|
|
44
|
+
|
|
45
|
+
/* Tighter chrome so the thread is the product */
|
|
46
|
+
#topbar-project { display: none; }
|
|
47
|
+
#project-view > .flex.bg-base-200 { /* tab strip */
|
|
48
|
+
min-height: 40px;
|
|
49
|
+
}
|
|
50
|
+
#project-view [data-tab] {
|
|
51
|
+
padding: 0.55rem 0.85rem !important;
|
|
52
|
+
font-size: 13px !important;
|
|
53
|
+
}
|
|
54
|
+
#project-notes-bar,
|
|
55
|
+
#session-notes-bar {
|
|
56
|
+
font-size: 11px;
|
|
57
|
+
padding: 0.35rem 0.7rem !important;
|
|
58
|
+
}
|
|
59
|
+
#messages {
|
|
60
|
+
padding: 0.65rem 0.45rem !important;
|
|
61
|
+
gap: 0.55rem !important;
|
|
62
|
+
}
|
|
63
|
+
.msg-row { gap: 3px; }
|
|
64
|
+
.msg-row.assistant > div:not(.msg-ts),
|
|
65
|
+
.msg-row.error > div:not(.msg-ts) {
|
|
66
|
+
max-width: 100% !important;
|
|
67
|
+
border-radius: 1rem !important;
|
|
68
|
+
border-top-left-radius: 0.3rem !important;
|
|
69
|
+
}
|
|
70
|
+
.msg-row.user > div:not(.msg-ts) {
|
|
71
|
+
max-width: 88% !important;
|
|
72
|
+
border-radius: 1rem !important;
|
|
73
|
+
border-top-right-radius: 0.3rem !important;
|
|
74
|
+
}
|
|
75
|
+
#sidebar-resize,
|
|
76
|
+
#sidebar-toggle {
|
|
77
|
+
display: none !important;
|
|
78
|
+
}
|
|
79
|
+
.md {
|
|
80
|
+
font-size: 0.9375rem;
|
|
81
|
+
line-height: 1.6;
|
|
82
|
+
}
|
|
83
|
+
.md h1, .md h2, .md h3 {
|
|
84
|
+
font-size: 1.02em !important;
|
|
85
|
+
padding: 0.05em 0 0.15em 0.45em !important;
|
|
86
|
+
border-left-width: 3px !important;
|
|
87
|
+
background: none !important;
|
|
88
|
+
border-bottom: none !important;
|
|
89
|
+
margin: 0.7em 0 0.35em !important;
|
|
90
|
+
}
|
|
91
|
+
.md :not(pre) > code {
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
display: inline;
|
|
94
|
+
font-size: 0.82em;
|
|
95
|
+
padding: 0.08em 0.32em;
|
|
96
|
+
}
|
|
97
|
+
.token-bar {
|
|
98
|
+
font-size: 10px;
|
|
99
|
+
line-height: 1.45;
|
|
100
|
+
padding: 0.15rem 0.45rem;
|
|
101
|
+
}
|
|
52
102
|
}
|
|
53
103
|
|
|
54
104
|
/* File row actions: hover-reveal on desktop only */
|
|
@@ -73,6 +123,20 @@ html, body {
|
|
|
73
123
|
max-height: 100dvh;
|
|
74
124
|
overflow: hidden;
|
|
75
125
|
}
|
|
126
|
+
/* iOS keyboard: shrink the app to the visual viewport so the composer
|
|
127
|
+
* sits above the keyboard instead of floating over Safari chrome. */
|
|
128
|
+
html.vv-keyboard,
|
|
129
|
+
html.vv-keyboard body {
|
|
130
|
+
height: var(--vvh, 100dvh);
|
|
131
|
+
max-height: var(--vvh, 100dvh);
|
|
132
|
+
}
|
|
133
|
+
html.vv-keyboard body {
|
|
134
|
+
position: fixed;
|
|
135
|
+
top: var(--vv-top, 0px);
|
|
136
|
+
left: 0;
|
|
137
|
+
right: 0;
|
|
138
|
+
width: 100%;
|
|
139
|
+
}
|
|
76
140
|
html.pwa-standalone,
|
|
77
141
|
html.pwa-standalone body {
|
|
78
142
|
background: oklch(var(--b1));
|
|
@@ -90,11 +154,19 @@ html.pwa-standalone #topbar {
|
|
|
90
154
|
* Inner #composer-row already has small padding; only add safe-area beyond that
|
|
91
155
|
* so we don't stack large empty strips under the input.
|
|
92
156
|
*/
|
|
157
|
+
#chat-form {
|
|
158
|
+
flex-shrink: 0;
|
|
159
|
+
display: flex;
|
|
160
|
+
flex-direction: column;
|
|
161
|
+
}
|
|
93
162
|
#chat-composer {
|
|
94
163
|
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
95
164
|
padding-left: env(safe-area-inset-left, 0px);
|
|
96
165
|
padding-right: env(safe-area-inset-right, 0px);
|
|
97
166
|
}
|
|
167
|
+
html.vv-keyboard #chat-composer {
|
|
168
|
+
padding-bottom: 0;
|
|
169
|
+
}
|
|
98
170
|
/* Welcome / machine picker full-screen panels */
|
|
99
171
|
html.pwa-standalone #welcome,
|
|
100
172
|
html.pwa-standalone #machine-picker {
|
|
@@ -883,14 +955,55 @@ html.pwa-standalone .ma-panel {
|
|
|
883
955
|
.diff-head { color: oklch(var(--bc)/.5); }
|
|
884
956
|
|
|
885
957
|
/* ── Chat composer ───────────────────────────────────────────────────────── */
|
|
886
|
-
/*
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
958
|
+
/* One calm surface holds the prompt and its actions, like a modern messenger. */
|
|
959
|
+
.composer-surface {
|
|
960
|
+
min-height: 0;
|
|
961
|
+
display: flex;
|
|
962
|
+
flex-direction: column;
|
|
963
|
+
overflow: hidden;
|
|
964
|
+
border: 1px solid color-mix(in oklch, var(--color-base-content) 16%, transparent);
|
|
965
|
+
border-radius: 1.35rem;
|
|
966
|
+
background: color-mix(in oklch, var(--color-base-100) 78%, var(--color-base-200));
|
|
967
|
+
box-shadow: 0 8px 24px color-mix(in oklch, black 12%, transparent), inset 0 1px 0 color-mix(in oklch, white 6%, transparent);
|
|
968
|
+
transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
|
|
969
|
+
}
|
|
970
|
+
.composer-surface:focus-within {
|
|
971
|
+
border-color: color-mix(in oklch, var(--color-primary) 62%, transparent);
|
|
972
|
+
box-shadow: 0 8px 28px color-mix(in oklch, black 15%, transparent), 0 0 0 3px color-mix(in oklch, var(--color-primary) 12%, transparent);
|
|
973
|
+
}
|
|
974
|
+
.composer-actions {
|
|
975
|
+
display: flex;
|
|
976
|
+
align-items: center;
|
|
977
|
+
justify-content: space-between;
|
|
978
|
+
gap: .75rem;
|
|
979
|
+
padding: 0;
|
|
980
|
+
}
|
|
981
|
+
.composer-tools, .composer-submit { display: flex; align-items: center; gap: .35rem; }
|
|
982
|
+
.composer-icon-btn,
|
|
983
|
+
.composer-send-btn,
|
|
984
|
+
.composer-stop-btn {
|
|
985
|
+
border: 0;
|
|
986
|
+
border-radius: 999px;
|
|
987
|
+
cursor: pointer;
|
|
988
|
+
transition: transform .15s ease, background .15s ease, color .15s ease;
|
|
989
|
+
}
|
|
990
|
+
.composer-icon-btn {
|
|
991
|
+
width: 36px;
|
|
992
|
+
height: 36px;
|
|
993
|
+
padding: 0;
|
|
994
|
+
color: color-mix(in oklch, var(--color-base-content) 72%, transparent);
|
|
995
|
+
background: transparent;
|
|
996
|
+
font-size: 17px;
|
|
997
|
+
line-height: 1;
|
|
893
998
|
}
|
|
999
|
+
.composer-icon-btn:hover { background: color-mix(in oklch, var(--color-base-content) 9%, transparent); color: var(--color-base-content); }
|
|
1000
|
+
.composer-send-btn, .composer-stop-btn { height: 36px; min-height: 36px; padding: 0 .95rem; font-size: .8125rem; font-weight: 700; }
|
|
1001
|
+
.composer-send-btn { color: oklch(var(--pc)); background: oklch(var(--p)); box-shadow: 0 3px 10px color-mix(in oklch, oklch(var(--p)) 38%, transparent); }
|
|
1002
|
+
.composer-send-btn:hover { transform: translateY(-1px); background: color-mix(in oklch, oklch(var(--p)) 88%, white); }
|
|
1003
|
+
.composer-send-btn.btn-warning { color: oklch(var(--wc)); background: oklch(var(--wa)); box-shadow: 0 3px 10px color-mix(in oklch, oklch(var(--wa)) 38%, transparent); }
|
|
1004
|
+
.composer-stop-btn { color: oklch(var(--ec)); background: oklch(var(--er)); }
|
|
1005
|
+
.composer-hint { color: color-mix(in oklch, var(--color-base-content) 43%, transparent); font-size: 11px; }
|
|
1006
|
+
.composer-surface:has(.textarea-warning) { border-color: color-mix(in oklch, var(--color-warning) 65%, transparent); }
|
|
894
1007
|
#chat-composer.chat-drop-active {
|
|
895
1008
|
outline: 2px dashed color-mix(in oklch, var(--color-primary) 70%, transparent);
|
|
896
1009
|
outline-offset: -2px;
|
|
@@ -900,16 +1013,17 @@ html.pwa-standalone .ma-panel {
|
|
|
900
1013
|
display: flex;
|
|
901
1014
|
}
|
|
902
1015
|
.chat-thumb {
|
|
903
|
-
width:
|
|
904
|
-
height:
|
|
905
|
-
border-radius:
|
|
1016
|
+
width: 64px;
|
|
1017
|
+
height: 64px;
|
|
1018
|
+
border-radius: 12px;
|
|
906
1019
|
overflow: hidden;
|
|
907
|
-
border: 1px solid color-mix(in oklch, var(--color-base-content)
|
|
1020
|
+
border: 1px solid color-mix(in oklch, var(--color-base-content) 12%, transparent);
|
|
908
1021
|
position: relative;
|
|
909
1022
|
flex-shrink: 0;
|
|
910
|
-
background: var(--color-base-100);
|
|
1023
|
+
background: color-mix(in oklch, var(--color-base-content) 4%, var(--color-base-100));
|
|
1024
|
+
box-shadow: 0 1px 2px color-mix(in oklch, black 12%, transparent);
|
|
911
1025
|
}
|
|
912
|
-
.chat-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
|
1026
|
+
.chat-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
913
1027
|
.chat-thumb-path {
|
|
914
1028
|
display: flex;
|
|
915
1029
|
align-items: center;
|
|
@@ -925,46 +1039,39 @@ html.pwa-standalone .ma-panel {
|
|
|
925
1039
|
background: color-mix(in oklch, var(--color-base-content) 6%, var(--color-base-100));
|
|
926
1040
|
}
|
|
927
1041
|
.chat-thumb-label {
|
|
928
|
-
|
|
929
|
-
left: 0;
|
|
930
|
-
bottom: 0;
|
|
931
|
-
right: 0;
|
|
932
|
-
font-size: 9px;
|
|
933
|
-
text-align: center;
|
|
934
|
-
background: rgba(0,0,0,.55);
|
|
935
|
-
color: #fff;
|
|
936
|
-
line-height: 1.2;
|
|
937
|
-
padding: 1px 0;
|
|
1042
|
+
display: none;
|
|
938
1043
|
}
|
|
939
1044
|
.chat-thumb-x {
|
|
940
1045
|
position: absolute;
|
|
941
|
-
top:
|
|
942
|
-
right:
|
|
943
|
-
background: rgba(
|
|
944
|
-
color: #
|
|
945
|
-
border:
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1046
|
+
top: 4px;
|
|
1047
|
+
right: 4px;
|
|
1048
|
+
background: rgba(255,255,255,.92);
|
|
1049
|
+
color: #334155;
|
|
1050
|
+
border: 1px solid rgba(15,23,42,.1);
|
|
1051
|
+
border-radius: 999px;
|
|
1052
|
+
font-size: 11px;
|
|
1053
|
+
width: 20px;
|
|
1054
|
+
height: 20px;
|
|
949
1055
|
cursor: pointer;
|
|
950
1056
|
line-height: 1;
|
|
951
1057
|
z-index: 1;
|
|
1058
|
+
box-shadow: 0 1px 3px rgba(15,23,42,.18);
|
|
952
1059
|
}
|
|
953
1060
|
#chat-input.chat-input,
|
|
954
1061
|
#chat-input {
|
|
955
1062
|
width: 100%;
|
|
956
|
-
min-height:
|
|
957
|
-
max-height:
|
|
958
|
-
padding: 0
|
|
959
|
-
border
|
|
960
|
-
border:
|
|
961
|
-
background:
|
|
1063
|
+
min-height: 32px;
|
|
1064
|
+
max-height: 72px;
|
|
1065
|
+
padding: 0;
|
|
1066
|
+
border: 0;
|
|
1067
|
+
border-radius: 1.25rem 1.25rem 0 0;
|
|
1068
|
+
background: transparent;
|
|
962
1069
|
color: var(--color-base-content);
|
|
963
1070
|
outline: none !important;
|
|
964
1071
|
box-shadow: none !important;
|
|
965
1072
|
--tw-ring-shadow: 0 0 #0000 !important;
|
|
966
1073
|
--tw-ring-offset-shadow: 0 0 #0000 !important;
|
|
967
|
-
transition:
|
|
1074
|
+
transition: background .15s ease;
|
|
968
1075
|
/* Avoid iOS inherited large text scaling making the box look oversized */
|
|
969
1076
|
-webkit-text-size-adjust: 100%;
|
|
970
1077
|
}
|
|
@@ -1035,7 +1142,7 @@ html.pwa-standalone .ma-panel {
|
|
|
1035
1142
|
#chat-input:focus-visible {
|
|
1036
1143
|
outline: none !important;
|
|
1037
1144
|
box-shadow: none !important;
|
|
1038
|
-
border-color:
|
|
1145
|
+
border-color: transparent;
|
|
1039
1146
|
}
|
|
1040
1147
|
#chat-input.textarea-warning {
|
|
1041
1148
|
border-color: color-mix(in oklch, var(--color-warning) 65%, transparent);
|
|
@@ -1136,6 +1243,26 @@ html.pwa-standalone .ma-panel {
|
|
|
1136
1243
|
}
|
|
1137
1244
|
.msg-row.user .msg-ts { text-align: right; }
|
|
1138
1245
|
|
|
1246
|
+
.token-bar {
|
|
1247
|
+
display: flex;
|
|
1248
|
+
align-items: flex-start;
|
|
1249
|
+
gap: 0.4rem;
|
|
1250
|
+
padding: 0.1rem 0.45rem;
|
|
1251
|
+
font-size: 10px;
|
|
1252
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1253
|
+
color: color-mix(in oklch, var(--color-base-content) 38%, transparent);
|
|
1254
|
+
line-height: 1.45;
|
|
1255
|
+
}
|
|
1256
|
+
.token-bar-mark {
|
|
1257
|
+
flex-shrink: 0;
|
|
1258
|
+
opacity: 0.55;
|
|
1259
|
+
}
|
|
1260
|
+
.token-bar-text {
|
|
1261
|
+
min-width: 0;
|
|
1262
|
+
overflow-wrap: anywhere;
|
|
1263
|
+
word-break: break-word;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1139
1266
|
/* ── Long message collapse ───────────────────────────────────────────────── */
|
|
1140
1267
|
.msg-card .msg-body { max-height: 8em; overflow: hidden; mask-image: linear-gradient(to bottom, black 60%, transparent 100%); }
|
|
1141
1268
|
.msg-card.open .msg-body { max-height: none; mask-image: none; }
|
|
@@ -1364,3 +1491,12 @@ html.pwa-standalone .ma-panel {
|
|
|
1364
1491
|
}
|
|
1365
1492
|
.ma-report-md { font-size: 13px; }
|
|
1366
1493
|
.ma-report-item { display: block; }
|
|
1494
|
+
|
|
1495
|
+
@media (max-width: 640px) {
|
|
1496
|
+
.md h1, .md h2, .md h3 {
|
|
1497
|
+
background: none !important;
|
|
1498
|
+
border-bottom: none !important;
|
|
1499
|
+
font-size: 1.02em !important;
|
|
1500
|
+
padding: 0.05em 0 0.15em 0.5em !important;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
package/public/sw.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* Agent UI service worker — cache shell assets only.
|
|
2
2
|
* Never intercepts WebSocket or mutates API semantics beyond offline fallback.
|
|
3
3
|
* Bump CACHE when shipping UI that must invalidate old shells. */
|
|
4
|
-
const CACHE = 'agent-ui-shell-
|
|
4
|
+
const CACHE = 'agent-ui-shell-v9';
|
|
5
5
|
const PRECACHE = [
|
|
6
6
|
'/',
|
|
7
7
|
'/index.html',
|
|
8
8
|
'/manifest.webmanifest',
|
|
9
|
-
'/style.css?v=ui-
|
|
9
|
+
'/style.css?v=ui-10',
|
|
10
10
|
'/style.css',
|
|
11
11
|
'/app.js',
|
|
12
12
|
'/icons/icon-192.png',
|
package/src/app.js
CHANGED
|
@@ -448,13 +448,27 @@ export function createApp() {
|
|
|
448
448
|
const ext = (req.headers['x-filename'] || 'image.png').split('.').pop().replace(/[^a-z0-9]/gi, '') || 'png';
|
|
449
449
|
const name = `claude-upload-${Date.now()}.${ext}`;
|
|
450
450
|
const dest = path.join(os.tmpdir(), name);
|
|
451
|
-
const
|
|
452
|
-
req.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
451
|
+
const chunks = [];
|
|
452
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
453
|
+
let body = Buffer.concat(chunks);
|
|
454
|
+
// Tolerate mobile share sheets that label Base64 text as a PNG. Only
|
|
455
|
+
// decode when the result has a real image signature.
|
|
456
|
+
const text = body.toString('utf8').trim();
|
|
457
|
+
const m = /^data:image\/[a-z0-9.+-]+;base64,([a-z0-9+/=\s]+)$/i.exec(text);
|
|
458
|
+
const encoded = (m?.[1] || text).replace(/\s/g, '');
|
|
459
|
+
if (/^[a-z0-9+/=]+$/i.test(encoded) && encoded.length % 4 !== 1) {
|
|
460
|
+
try {
|
|
461
|
+
const decoded = Buffer.from(encoded, 'base64');
|
|
462
|
+
const image = decoded.length >= 3 && (
|
|
463
|
+
(decoded[0] === 0x89 && decoded[1] === 0x50 && decoded[2] === 0x4e)
|
|
464
|
+
|| (decoded[0] === 0xff && decoded[1] === 0xd8 && decoded[2] === 0xff)
|
|
465
|
+
|| decoded.subarray(0, 6).toString() === 'GIF87a'
|
|
466
|
+
|| decoded.subarray(8, 12).toString() === 'WEBP'
|
|
467
|
+
);
|
|
468
|
+
if (image) body = decoded;
|
|
469
|
+
} catch { /* preserve the original upload */ }
|
|
470
|
+
}
|
|
471
|
+
await fs.writeFile(dest, body);
|
|
458
472
|
res.json({ path: dest });
|
|
459
473
|
})().catch(e => res.status(500).json({ error: e.message }));
|
|
460
474
|
});
|