open-agents-ai 0.187.456 → 0.187.458
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/dist/index.js +2646 -460
- package/npm-shrinkwrap.json +11 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13007,8 +13007,8 @@ function deleteCustomToolDefinition(name10, scope, repoRoot) {
|
|
|
13007
13007
|
const dir = scope === "project" && repoRoot ? projectToolsDir(repoRoot) : globalToolsDir();
|
|
13008
13008
|
const filePath = join18(dir, `${name10}.json`);
|
|
13009
13009
|
if (existsSync13(filePath)) {
|
|
13010
|
-
const { unlinkSync:
|
|
13011
|
-
|
|
13010
|
+
const { unlinkSync: unlinkSync25 } = __require("node:fs");
|
|
13011
|
+
unlinkSync25(filePath);
|
|
13012
13012
|
return true;
|
|
13013
13013
|
}
|
|
13014
13014
|
return false;
|
|
@@ -250314,8 +250314,8 @@ var init_browser_action = __esm({
|
|
|
250314
250314
|
const afterDom = await apiCall("/dom", "GET");
|
|
250315
250315
|
const afterTitle = (afterDom.dom || "").match(/<title[^>]*>([^<]*)<\/title>/i)?.[1] || "";
|
|
250316
250316
|
try {
|
|
250317
|
-
const { unlinkSync:
|
|
250318
|
-
|
|
250317
|
+
const { unlinkSync: unlinkSync25 } = await import("node:fs");
|
|
250318
|
+
unlinkSync25(imagePath);
|
|
250319
250319
|
} catch {
|
|
250320
250320
|
}
|
|
250321
250321
|
return {
|
|
@@ -523527,7 +523527,7 @@ ${result}`
|
|
|
523527
523527
|
let resizedBase64 = null;
|
|
523528
523528
|
try {
|
|
523529
523529
|
const { execSync: execSync57 } = await import("node:child_process");
|
|
523530
|
-
const { writeFileSync: writeFileSync56, readFileSync: readFileSync77, unlinkSync:
|
|
523530
|
+
const { writeFileSync: writeFileSync56, readFileSync: readFileSync77, unlinkSync: unlinkSync25 } = await import("node:fs");
|
|
523531
523531
|
const { join: join115 } = await import("node:path");
|
|
523532
523532
|
const { tmpdir: tmpdir22 } = await import("node:os");
|
|
523533
523533
|
const tmpIn = join115(tmpdir22(), `oa_img_in_${Date.now()}.png`);
|
|
@@ -523540,11 +523540,11 @@ ${result}`
|
|
|
523540
523540
|
const resizedBuf = readFileSync77(tmpOut);
|
|
523541
523541
|
resizedBase64 = `data:image/jpeg;base64,${resizedBuf.toString("base64")}`;
|
|
523542
523542
|
try {
|
|
523543
|
-
|
|
523543
|
+
unlinkSync25(tmpIn);
|
|
523544
523544
|
} catch {
|
|
523545
523545
|
}
|
|
523546
523546
|
try {
|
|
523547
|
-
|
|
523547
|
+
unlinkSync25(tmpOut);
|
|
523548
523548
|
} catch {
|
|
523549
523549
|
}
|
|
523550
523550
|
} catch {
|
|
@@ -574787,66 +574787,137 @@ function getWebUI() {
|
|
|
574787
574787
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
574788
574788
|
<title>Open Agents</title>
|
|
574789
574789
|
<style>
|
|
574790
|
+
/* ─── Open WebUI-shaped design tokens (OWUI-1) ────────────────────
|
|
574791
|
+
* Replaces ~80 hardcoded color literals with CSS custom properties.
|
|
574792
|
+
* Palette matches openwebui's neutral grayscale + functional blue
|
|
574793
|
+
* accent. Brand gold preserved as --color-brand for OA-only marks.
|
|
574794
|
+
* Typography flips body to Inter (UI) + JetBrains Mono (code only),
|
|
574795
|
+
* leaving the terminal aesthetic only where we explicitly opt in.
|
|
574796
|
+
*
|
|
574797
|
+
* Source: /tmp/openwebui-ref/src/tailwind.css @theme + parity audit
|
|
574798
|
+
* at .aiwg/owui-parity.md.
|
|
574799
|
+
*/
|
|
574800
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
|
574801
|
+
|
|
574802
|
+
:root {
|
|
574803
|
+
/* Surfaces — neutral grayscale, openwebui dark default */
|
|
574804
|
+
--color-bg: oklch(0.16 0 0); /* gray-950, body background */
|
|
574805
|
+
--color-bg-elevated: oklch(0.20 0 0); /* gray-900, panels + headers */
|
|
574806
|
+
--color-bg-input: oklch(0.27 0 0); /* gray-850, inputs + chips + buttons */
|
|
574807
|
+
--color-bg-hover: oklch(0.32 0 0); /* gray-800, hover surfaces */
|
|
574808
|
+
|
|
574809
|
+
/* Foregrounds */
|
|
574810
|
+
--color-fg: oklch(0.94 0 0); /* primary text */
|
|
574811
|
+
--color-fg-muted: oklch(0.69 0 0); /* secondary text — gray-500 */
|
|
574812
|
+
--color-fg-subtle: oklch(0.51 0 0); /* tertiary — gray-600 */
|
|
574813
|
+
--color-fg-faint: oklch(0.42 0 0); /* faint hints — gray-700 */
|
|
574814
|
+
|
|
574815
|
+
/* Borders */
|
|
574816
|
+
--color-border: oklch(0.27 0 0); /* default — gray-850 */
|
|
574817
|
+
--color-border-strong:oklch(0.32 0 0); /* emphasized — gray-800 */
|
|
574818
|
+
|
|
574819
|
+
/* Functional accents (openwebui pattern: blue for actions, sparing) */
|
|
574820
|
+
--color-accent: #3b82f6; /* blue-500 — primary actions */
|
|
574821
|
+
--color-accent-hover: #60a5fa; /* blue-400 */
|
|
574822
|
+
--color-success: #22c55e; /* green-500 */
|
|
574823
|
+
--color-warning: #f59e0b; /* amber-500 */
|
|
574824
|
+
--color-error: #ef4444; /* red-500 */
|
|
574825
|
+
--color-info: #14b8a6; /* teal-500 — checkin/info */
|
|
574826
|
+
|
|
574827
|
+
/* OA brand — kept narrow scope for OA-only marks (active tab strip,
|
|
574828
|
+
* brand glyphs, status accents). NOT the dominant accent anymore. */
|
|
574829
|
+
--color-brand: #b2920a;
|
|
574830
|
+
--color-brand-hover: #d4ac0e;
|
|
574831
|
+
|
|
574832
|
+
/* Typography */
|
|
574833
|
+
--font-ui: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
574834
|
+
--font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', ui-monospace, monospace;
|
|
574835
|
+
|
|
574836
|
+
/* Radii */
|
|
574837
|
+
--radius-sm: 6px;
|
|
574838
|
+
--radius-md: 8px;
|
|
574839
|
+
--radius-lg: 12px; /* openwebui's rounded-xl */
|
|
574840
|
+
--radius-pill: 9999px;
|
|
574841
|
+
}
|
|
574842
|
+
|
|
574843
|
+
/* Light theme — opt in via [data-theme=light] on html.
|
|
574844
|
+
* Minimum-effort palette parity (OWUI-1 scope). Phase OWUI-3 polishes. */
|
|
574845
|
+
html[data-theme=light] {
|
|
574846
|
+
--color-bg: oklch(0.98 0 0);
|
|
574847
|
+
--color-bg-elevated: oklch(0.94 0 0);
|
|
574848
|
+
--color-bg-input: oklch(0.92 0 0);
|
|
574849
|
+
--color-bg-hover: oklch(0.85 0 0);
|
|
574850
|
+
--color-fg: oklch(0.16 0 0);
|
|
574851
|
+
--color-fg-muted: oklch(0.42 0 0);
|
|
574852
|
+
--color-fg-subtle: oklch(0.51 0 0);
|
|
574853
|
+
--color-fg-faint: oklch(0.69 0 0);
|
|
574854
|
+
--color-border: oklch(0.85 0 0);
|
|
574855
|
+
--color-border-strong:oklch(0.77 0 0);
|
|
574856
|
+
}
|
|
574857
|
+
|
|
574790
574858
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
574791
574859
|
|
|
574792
574860
|
/* ─── Cross-browser custom scrollbars ─────────────────────────────
|
|
574793
|
-
*
|
|
574794
|
-
* scrollable container in the app via the universal selector.
|
|
574795
|
-
* Firefox uses the standard scrollbar-* properties; WebKit/Blink
|
|
574796
|
-
* uses the ::-webkit-scrollbar pseudo-elements. Both render the
|
|
574797
|
-
* same brand colors.
|
|
574861
|
+
* Token-driven so theme changes cascade. Subtle hover.
|
|
574798
574862
|
*/
|
|
574799
574863
|
html {
|
|
574800
574864
|
scrollbar-width: thin;
|
|
574801
|
-
scrollbar-color:
|
|
574865
|
+
scrollbar-color: var(--color-border-strong) var(--color-bg-elevated);
|
|
574802
574866
|
}
|
|
574803
574867
|
*::-webkit-scrollbar {
|
|
574804
574868
|
width: 10px;
|
|
574805
574869
|
height: 10px;
|
|
574806
574870
|
}
|
|
574807
574871
|
*::-webkit-scrollbar-track {
|
|
574808
|
-
background:
|
|
574872
|
+
background: var(--color-bg-elevated);
|
|
574809
574873
|
border-radius: 0;
|
|
574810
574874
|
}
|
|
574811
574875
|
*::-webkit-scrollbar-thumb {
|
|
574812
|
-
background:
|
|
574813
|
-
border-radius:
|
|
574814
|
-
border: 2px solid
|
|
574876
|
+
background: var(--color-border-strong);
|
|
574877
|
+
border-radius: var(--radius-sm);
|
|
574878
|
+
border: 2px solid var(--color-bg-elevated);
|
|
574815
574879
|
}
|
|
574816
574880
|
*::-webkit-scrollbar-thumb:hover {
|
|
574817
|
-
background:
|
|
574881
|
+
background: var(--color-fg-faint);
|
|
574818
574882
|
}
|
|
574819
574883
|
*::-webkit-scrollbar-corner {
|
|
574820
|
-
background:
|
|
574884
|
+
background: var(--color-bg-elevated);
|
|
574821
574885
|
}
|
|
574822
574886
|
|
|
574823
574887
|
body {
|
|
574824
|
-
font-family:
|
|
574825
|
-
background:
|
|
574826
|
-
color:
|
|
574888
|
+
font-family: var(--font-ui);
|
|
574889
|
+
background: var(--color-bg);
|
|
574890
|
+
color: var(--color-fg);
|
|
574827
574891
|
display: flex;
|
|
574828
574892
|
flex-direction: column;
|
|
574829
574893
|
height: 100vh;
|
|
574830
574894
|
overflow: hidden;
|
|
574831
574895
|
scrollbar-width: thin;
|
|
574832
|
-
scrollbar-color:
|
|
574896
|
+
scrollbar-color: var(--color-border-strong) var(--color-bg-elevated);
|
|
574897
|
+
font-size: 14px;
|
|
574898
|
+
-webkit-font-smoothing: antialiased;
|
|
574899
|
+
}
|
|
574900
|
+
|
|
574901
|
+
/* Anywhere we explicitly want the terminal/code feel, opt in via .mono */
|
|
574902
|
+
.mono, code, pre, kbd, samp, tt {
|
|
574903
|
+
font-family: var(--font-mono);
|
|
574833
574904
|
}
|
|
574834
574905
|
#header {
|
|
574835
574906
|
display: flex;
|
|
574836
574907
|
align-items: center;
|
|
574837
574908
|
gap: 12px;
|
|
574838
574909
|
padding: 8px 16px;
|
|
574839
|
-
background:
|
|
574840
|
-
border-bottom: 1px solid
|
|
574910
|
+
background: var(--color-bg-elevated);
|
|
574911
|
+
border-bottom: 1px solid var(--color-bg-input);
|
|
574841
574912
|
flex-shrink: 0;
|
|
574842
574913
|
}
|
|
574843
|
-
#header .accent { color:
|
|
574844
|
-
#header .status { font-size: 0.7rem; color:
|
|
574845
|
-
#header .status.live { color:
|
|
574914
|
+
#header .accent { color: var(--color-brand); font-weight: bold; font-size: 0.8rem; }
|
|
574915
|
+
#header .status { font-size: 0.7rem; color: var(--color-fg-faint); }
|
|
574916
|
+
#header .status.live { color: var(--color-brand); }
|
|
574846
574917
|
#header select {
|
|
574847
|
-
background:
|
|
574848
|
-
border: 1px solid
|
|
574849
|
-
color:
|
|
574918
|
+
background: var(--color-bg-input);
|
|
574919
|
+
border: 1px solid var(--color-border);
|
|
574920
|
+
color: var(--color-fg);
|
|
574850
574921
|
padding: 4px 8px;
|
|
574851
574922
|
border-radius: 3px;
|
|
574852
574923
|
font-family: inherit;
|
|
@@ -574857,9 +574928,9 @@ body {
|
|
|
574857
574928
|
text-overflow: ellipsis;
|
|
574858
574929
|
}
|
|
574859
574930
|
#header .key-btn {
|
|
574860
|
-
background:
|
|
574861
|
-
border: 1px solid
|
|
574862
|
-
color:
|
|
574931
|
+
background: var(--color-bg-input);
|
|
574932
|
+
border: 1px solid var(--color-border);
|
|
574933
|
+
color: var(--color-brand);
|
|
574863
574934
|
padding: 4px 10px;
|
|
574864
574935
|
border-radius: 3px;
|
|
574865
574936
|
font-family: inherit;
|
|
@@ -574867,7 +574938,7 @@ body {
|
|
|
574867
574938
|
cursor: pointer;
|
|
574868
574939
|
transition: background 0.15s;
|
|
574869
574940
|
}
|
|
574870
|
-
#header .key-btn:hover { background:
|
|
574941
|
+
#header .key-btn:hover { background: var(--color-border); }
|
|
574871
574942
|
#conversation {
|
|
574872
574943
|
flex: 1;
|
|
574873
574944
|
overflow-y: auto;
|
|
@@ -574877,20 +574948,20 @@ body {
|
|
|
574877
574948
|
gap: 4px;
|
|
574878
574949
|
}
|
|
574879
574950
|
.msg { padding: 6px 0; font-size: 0.82rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
|
|
574880
|
-
.msg.user { color:
|
|
574881
|
-
.msg.user::before { content: '\\25B8 '; color:
|
|
574882
|
-
.msg.assistant { color:
|
|
574883
|
-
.msg.assistant::before { content: '\\25B9 '; color:
|
|
574884
|
-
.msg.system { color:
|
|
574951
|
+
.msg.user { color: var(--color-fg-muted); }
|
|
574952
|
+
.msg.user::before { content: '\\25B8 '; color: var(--color-fg-faint); }
|
|
574953
|
+
.msg.assistant { color: var(--color-brand); }
|
|
574954
|
+
.msg.assistant::before { content: '\\25B9 '; color: var(--color-brand); }
|
|
574955
|
+
.msg.system { color: var(--color-fg-faint); font-size: 0.7rem; }
|
|
574885
574956
|
.msg code {
|
|
574886
|
-
background:
|
|
574957
|
+
background: var(--color-bg-input);
|
|
574887
574958
|
padding: 1px 4px;
|
|
574888
574959
|
border-radius: 2px;
|
|
574889
574960
|
font-size: 0.78rem;
|
|
574890
574961
|
}
|
|
574891
574962
|
.msg pre {
|
|
574892
|
-
background:
|
|
574893
|
-
border: 1px solid
|
|
574963
|
+
background: var(--color-bg-elevated);
|
|
574964
|
+
border: 1px solid var(--color-bg-input);
|
|
574894
574965
|
border-radius: 3px;
|
|
574895
574966
|
padding: 8px 12px;
|
|
574896
574967
|
margin: 6px 0;
|
|
@@ -574903,9 +574974,9 @@ body {
|
|
|
574903
574974
|
position: absolute;
|
|
574904
574975
|
top: 4px;
|
|
574905
574976
|
right: 4px;
|
|
574906
|
-
background:
|
|
574907
|
-
border: 1px solid
|
|
574908
|
-
color:
|
|
574977
|
+
background: var(--color-bg-input);
|
|
574978
|
+
border: 1px solid var(--color-border);
|
|
574979
|
+
color: var(--color-brand);
|
|
574909
574980
|
padding: 2px 8px;
|
|
574910
574981
|
border-radius: 2px;
|
|
574911
574982
|
font-family: inherit;
|
|
@@ -574923,21 +574994,21 @@ body {
|
|
|
574923
574994
|
.msg-actions button {
|
|
574924
574995
|
background: none;
|
|
574925
574996
|
border: none;
|
|
574926
|
-
color:
|
|
574997
|
+
color: var(--color-fg-faint);
|
|
574927
574998
|
font-family: inherit;
|
|
574928
574999
|
font-size: 0.6rem;
|
|
574929
575000
|
cursor: pointer;
|
|
574930
575001
|
padding: 0;
|
|
574931
575002
|
}
|
|
574932
|
-
.msg-actions button:hover { color:
|
|
575003
|
+
.msg-actions button:hover { color: var(--color-brand); }
|
|
574933
575004
|
#footer {
|
|
574934
575005
|
display: flex;
|
|
574935
575006
|
flex-direction: column;
|
|
574936
575007
|
/* gap: 0 — was 4px, removed per user request to tighten footer */
|
|
574937
575008
|
gap: 0;
|
|
574938
575009
|
padding: 0;
|
|
574939
|
-
background:
|
|
574940
|
-
border-top: 1px solid
|
|
575010
|
+
background: var(--color-bg-elevated);
|
|
575011
|
+
border-top: 1px solid var(--color-bg-input);
|
|
574941
575012
|
flex-shrink: 0;
|
|
574942
575013
|
position: relative;
|
|
574943
575014
|
}
|
|
@@ -574947,14 +575018,14 @@ body {
|
|
|
574947
575018
|
gap: 6px;
|
|
574948
575019
|
padding: 4px 16px;
|
|
574949
575020
|
min-height: 20px;
|
|
574950
|
-
background:
|
|
574951
|
-
border-bottom: 1px solid
|
|
575021
|
+
background: var(--color-bg);
|
|
575022
|
+
border-bottom: 1px solid var(--color-bg-input);
|
|
574952
575023
|
overflow-x: auto;
|
|
574953
575024
|
scrollbar-width: none;
|
|
574954
575025
|
}
|
|
574955
575026
|
#processes-row::-webkit-scrollbar { display: none; }
|
|
574956
575027
|
#processes-row .proc-label {
|
|
574957
|
-
color:
|
|
575028
|
+
color: var(--color-fg-faint);
|
|
574958
575029
|
font-size: 0.55rem;
|
|
574959
575030
|
text-transform: uppercase;
|
|
574960
575031
|
letter-spacing: 0.08em;
|
|
@@ -574975,12 +575046,12 @@ body {
|
|
|
574975
575046
|
gap: 6px 6px; /* row gap + column gap once wrapping kicks in */
|
|
574976
575047
|
padding: 4px 16px;
|
|
574977
575048
|
min-height: 22px;
|
|
574978
|
-
background:
|
|
574979
|
-
border-bottom: 1px solid
|
|
575049
|
+
background: var(--color-bg);
|
|
575050
|
+
border-bottom: 1px solid var(--color-bg-input);
|
|
574980
575051
|
/* Wrap instead of horizontal scroll so all tasks are visible */
|
|
574981
575052
|
}
|
|
574982
575053
|
#tasks-row .tasks-label {
|
|
574983
|
-
color:
|
|
575054
|
+
color: var(--color-fg-faint);
|
|
574984
575055
|
font-size: 0.55rem;
|
|
574985
575056
|
text-transform: uppercase;
|
|
574986
575057
|
letter-spacing: 0.08em;
|
|
@@ -574993,8 +575064,8 @@ body {
|
|
|
574993
575064
|
gap: 4px;
|
|
574994
575065
|
padding: 2px 6px;
|
|
574995
575066
|
border-radius: 3px;
|
|
574996
|
-
background:
|
|
574997
|
-
border: 1px solid
|
|
575067
|
+
background: var(--color-bg-elevated);
|
|
575068
|
+
border: 1px solid var(--color-bg-input);
|
|
574998
575069
|
font-size: 0.62rem;
|
|
574999
575070
|
white-space: nowrap;
|
|
575000
575071
|
flex-shrink: 0;
|
|
@@ -575009,15 +575080,15 @@ body {
|
|
|
575009
575080
|
text-overflow: ellipsis;
|
|
575010
575081
|
white-space: nowrap;
|
|
575011
575082
|
}
|
|
575012
|
-
#tasks-row .task-item.pending { color:
|
|
575013
|
-
#tasks-row .task-item.pending .mark { color:
|
|
575014
|
-
#tasks-row .task-item.in_progress { color:
|
|
575015
|
-
#tasks-row .task-item.in_progress .mark { color:
|
|
575083
|
+
#tasks-row .task-item.pending { color: var(--color-fg-faint); }
|
|
575084
|
+
#tasks-row .task-item.pending .mark { color: var(--color-fg-faint); }
|
|
575085
|
+
#tasks-row .task-item.in_progress { color: var(--color-brand); border-color: var(--color-brand); box-shadow: 0 0 4px rgba(178,146,10,0.3); }
|
|
575086
|
+
#tasks-row .task-item.in_progress .mark { color: var(--color-brand); }
|
|
575016
575087
|
#tasks-row .task-item.completed { color: #4a7a4a; }
|
|
575017
575088
|
#tasks-row .task-item.completed .mark { color: #5fa55f; }
|
|
575018
575089
|
#tasks-row .task-item.completed .label { text-decoration: line-through; opacity: 0.7; }
|
|
575019
|
-
#tasks-row .task-item.blocked { color:
|
|
575020
|
-
#tasks-row .task-item.blocked .mark { color:
|
|
575090
|
+
#tasks-row .task-item.blocked { color: var(--color-error); border-color: var(--color-error); }
|
|
575091
|
+
#tasks-row .task-item.blocked .mark { color: var(--color-error); }
|
|
575021
575092
|
|
|
575022
575093
|
/* WO-TASK-02 — session topbar (chat sessions select + agent runs select).
|
|
575023
575094
|
Sits above each panel's content; shares the processes-row dark strip
|
|
@@ -575028,22 +575099,22 @@ body {
|
|
|
575028
575099
|
gap: 8px;
|
|
575029
575100
|
padding: 4px 16px;
|
|
575030
575101
|
min-height: 22px;
|
|
575031
|
-
background:
|
|
575032
|
-
border-bottom: 1px solid
|
|
575102
|
+
background: var(--color-bg);
|
|
575103
|
+
border-bottom: 1px solid var(--color-bg-input);
|
|
575033
575104
|
flex-shrink: 0;
|
|
575034
575105
|
font-size: 0.62rem;
|
|
575035
575106
|
}
|
|
575036
575107
|
.session-topbar .topbar-label {
|
|
575037
|
-
color:
|
|
575108
|
+
color: var(--color-fg-faint);
|
|
575038
575109
|
font-size: 0.55rem;
|
|
575039
575110
|
text-transform: uppercase;
|
|
575040
575111
|
letter-spacing: 0.08em;
|
|
575041
575112
|
white-space: nowrap;
|
|
575042
575113
|
}
|
|
575043
575114
|
.session-topbar select {
|
|
575044
|
-
background:
|
|
575045
|
-
border: 1px solid
|
|
575046
|
-
color:
|
|
575115
|
+
background: var(--color-bg-input);
|
|
575116
|
+
border: 1px solid var(--color-border);
|
|
575117
|
+
color: var(--color-fg);
|
|
575047
575118
|
padding: 2px 8px;
|
|
575048
575119
|
border-radius: 3px;
|
|
575049
575120
|
font-family: inherit;
|
|
@@ -575052,9 +575123,9 @@ body {
|
|
|
575052
575123
|
max-width: 280px;
|
|
575053
575124
|
}
|
|
575054
575125
|
.session-topbar button {
|
|
575055
|
-
background:
|
|
575056
|
-
border: 1px solid
|
|
575057
|
-
color:
|
|
575126
|
+
background: var(--color-bg-input);
|
|
575127
|
+
border: 1px solid var(--color-border);
|
|
575128
|
+
color: var(--color-brand);
|
|
575058
575129
|
padding: 2px 8px;
|
|
575059
575130
|
border-radius: 3px;
|
|
575060
575131
|
font-family: inherit;
|
|
@@ -575076,14 +575147,14 @@ body {
|
|
|
575076
575147
|
}
|
|
575077
575148
|
.proc-dot.fading { opacity: 0.3; }
|
|
575078
575149
|
/* Dot colors by process type */
|
|
575079
|
-
.proc-dot.run { background:
|
|
575080
|
-
.proc-dot.tool { background:
|
|
575150
|
+
.proc-dot.run { background: var(--color-success); color: var(--color-success); } /* agentic runs = green */
|
|
575151
|
+
.proc-dot.tool { background: var(--color-brand); color: var(--color-brand); } /* tool calls = gold */
|
|
575081
575152
|
.proc-dot.skill { background: #c94ec9; color: #c94ec9; } /* skills = magenta */
|
|
575082
575153
|
.proc-dot.mcp { background: #4ec9c9; color: #4ec9c9; } /* mcp = cyan */
|
|
575083
575154
|
.proc-dot.cron { background: #c9944e; color: #c9944e; } /* cron = orange */
|
|
575084
575155
|
.proc-dot.memory { background: #4e94c9; color: #4e94c9; } /* memory = blue */
|
|
575085
575156
|
.proc-dot.incident{ background: #c94e4e; color: #c94e4e; } /* incidents = red */
|
|
575086
|
-
.proc-dot.other { background:
|
|
575157
|
+
.proc-dot.other { background: var(--color-fg-muted); color: var(--color-fg-muted); }
|
|
575087
575158
|
.proc-dot.running { box-shadow: 0 0 4px currentColor; animation: pulse 1.2s infinite; }
|
|
575088
575159
|
@keyframes pulse {
|
|
575089
575160
|
0%,100% { opacity: 1; }
|
|
@@ -575095,8 +575166,8 @@ body {
|
|
|
575095
575166
|
left: 16px;
|
|
575096
575167
|
right: 16px;
|
|
575097
575168
|
margin-bottom: 6px;
|
|
575098
|
-
background:
|
|
575099
|
-
border: 1px solid
|
|
575169
|
+
background: var(--color-bg-elevated);
|
|
575170
|
+
border: 1px solid var(--color-brand);
|
|
575100
575171
|
border-radius: 4px;
|
|
575101
575172
|
padding: 12px 16px;
|
|
575102
575173
|
box-shadow: 0 -4px 20px rgba(0,0,0,0.6);
|
|
@@ -575108,7 +575179,7 @@ body {
|
|
|
575108
575179
|
}
|
|
575109
575180
|
#proc-popover.visible { display: block; }
|
|
575110
575181
|
#proc-popover .popover-title {
|
|
575111
|
-
color:
|
|
575182
|
+
color: var(--color-brand);
|
|
575112
575183
|
font-size: 0.75rem;
|
|
575113
575184
|
font-weight: bold;
|
|
575114
575185
|
display: flex;
|
|
@@ -575119,12 +575190,12 @@ body {
|
|
|
575119
575190
|
#proc-popover .popover-close {
|
|
575120
575191
|
background: none;
|
|
575121
575192
|
border: none;
|
|
575122
|
-
color:
|
|
575193
|
+
color: var(--color-fg-subtle);
|
|
575123
575194
|
cursor: pointer;
|
|
575124
575195
|
font-size: 1rem;
|
|
575125
575196
|
padding: 0 4px;
|
|
575126
575197
|
}
|
|
575127
|
-
#proc-popover .popover-close:hover { color:
|
|
575198
|
+
#proc-popover .popover-close:hover { color: var(--color-brand); }
|
|
575128
575199
|
#proc-popover .popover-row {
|
|
575129
575200
|
display: flex;
|
|
575130
575201
|
justify-content: space-between;
|
|
@@ -575132,26 +575203,26 @@ body {
|
|
|
575132
575203
|
padding: 2px 0;
|
|
575133
575204
|
color: #999;
|
|
575134
575205
|
}
|
|
575135
|
-
#proc-popover .popover-row .k { color:
|
|
575136
|
-
#proc-popover .popover-row .v { color:
|
|
575206
|
+
#proc-popover .popover-row .k { color: var(--color-brand); min-width: 90px; }
|
|
575207
|
+
#proc-popover .popover-row .v { color: var(--color-fg); word-break: break-all; text-align: right; }
|
|
575137
575208
|
#proc-popover .popover-actions {
|
|
575138
575209
|
display: flex;
|
|
575139
575210
|
gap: 8px;
|
|
575140
575211
|
margin-top: 10px;
|
|
575141
575212
|
padding-top: 8px;
|
|
575142
|
-
border-top: 1px solid
|
|
575213
|
+
border-top: 1px solid var(--color-bg-input);
|
|
575143
575214
|
}
|
|
575144
575215
|
#proc-popover .popover-actions button {
|
|
575145
|
-
background:
|
|
575146
|
-
border: 1px solid
|
|
575147
|
-
color:
|
|
575216
|
+
background: var(--color-bg-input);
|
|
575217
|
+
border: 1px solid var(--color-border);
|
|
575218
|
+
color: var(--color-brand);
|
|
575148
575219
|
padding: 4px 10px;
|
|
575149
575220
|
border-radius: 3px;
|
|
575150
575221
|
font-family: inherit;
|
|
575151
575222
|
font-size: 0.65rem;
|
|
575152
575223
|
cursor: pointer;
|
|
575153
575224
|
}
|
|
575154
|
-
#proc-popover .popover-actions button:hover { background:
|
|
575225
|
+
#proc-popover .popover-actions button:hover { background: var(--color-border); }
|
|
575155
575226
|
#proc-popover .popover-actions .kill {
|
|
575156
575227
|
border-color: #c94e4e;
|
|
575157
575228
|
color: #c94e4e;
|
|
@@ -575164,15 +575235,15 @@ body {
|
|
|
575164
575235
|
align-items: flex-end;
|
|
575165
575236
|
gap: 10px;
|
|
575166
575237
|
padding: 8px 16px 8px;
|
|
575167
|
-
background:
|
|
575238
|
+
background: var(--color-bg-elevated);
|
|
575168
575239
|
}
|
|
575169
575240
|
#input-area {
|
|
575170
575241
|
flex: 1;
|
|
575171
|
-
background:
|
|
575172
|
-
border: 1px solid
|
|
575242
|
+
background: var(--color-bg-input);
|
|
575243
|
+
border: 1px solid var(--color-border);
|
|
575173
575244
|
border-radius: 3px;
|
|
575174
575245
|
padding: 8px 12px;
|
|
575175
|
-
color:
|
|
575246
|
+
color: var(--color-fg);
|
|
575176
575247
|
font-family: inherit;
|
|
575177
575248
|
font-size: 0.82rem;
|
|
575178
575249
|
resize: none;
|
|
@@ -575181,11 +575252,11 @@ body {
|
|
|
575181
575252
|
line-height: 1.4;
|
|
575182
575253
|
outline: none;
|
|
575183
575254
|
}
|
|
575184
|
-
#input-area:focus { border-color:
|
|
575255
|
+
#input-area:focus { border-color: var(--color-brand); }
|
|
575185
575256
|
#send-btn {
|
|
575186
|
-
background:
|
|
575187
|
-
border: 1px solid
|
|
575188
|
-
color:
|
|
575257
|
+
background: var(--color-bg-input);
|
|
575258
|
+
border: 1px solid var(--color-border);
|
|
575259
|
+
color: var(--color-brand);
|
|
575189
575260
|
padding: 10px 16px;
|
|
575190
575261
|
border-radius: 3px;
|
|
575191
575262
|
font-family: inherit;
|
|
@@ -575194,27 +575265,27 @@ body {
|
|
|
575194
575265
|
transition: background 0.15s;
|
|
575195
575266
|
flex-shrink: 0;
|
|
575196
575267
|
}
|
|
575197
|
-
#send-btn:hover { background:
|
|
575268
|
+
#send-btn:hover { background: var(--color-border); }
|
|
575198
575269
|
#send-btn:disabled { opacity: 0.3; cursor: default; }
|
|
575199
575270
|
#system-prompt-toggle {
|
|
575200
575271
|
font-size: 0.65rem;
|
|
575201
|
-
color:
|
|
575272
|
+
color: var(--color-fg-faint);
|
|
575202
575273
|
cursor: pointer;
|
|
575203
575274
|
padding: 4px 0;
|
|
575204
575275
|
}
|
|
575205
|
-
#system-prompt-toggle:hover { color:
|
|
575276
|
+
#system-prompt-toggle:hover { color: var(--color-brand); }
|
|
575206
575277
|
#system-prompt-area {
|
|
575207
575278
|
display: none;
|
|
575208
575279
|
padding: 0 16px 8px;
|
|
575209
|
-
background:
|
|
575280
|
+
background: var(--color-bg-elevated);
|
|
575210
575281
|
}
|
|
575211
575282
|
#system-prompt-area textarea {
|
|
575212
575283
|
width: 100%;
|
|
575213
|
-
background:
|
|
575214
|
-
border: 1px solid
|
|
575284
|
+
background: var(--color-bg-input);
|
|
575285
|
+
border: 1px solid var(--color-border);
|
|
575215
575286
|
border-radius: 3px;
|
|
575216
575287
|
padding: 6px 10px;
|
|
575217
|
-
color:
|
|
575288
|
+
color: var(--color-fg);
|
|
575218
575289
|
font-family: inherit;
|
|
575219
575290
|
font-size: 0.7rem;
|
|
575220
575291
|
resize: vertical;
|
|
@@ -575232,21 +575303,21 @@ body {
|
|
|
575232
575303
|
}
|
|
575233
575304
|
#key-modal.visible { display: flex; }
|
|
575234
575305
|
#key-modal .modal {
|
|
575235
|
-
background:
|
|
575236
|
-
border: 1px solid
|
|
575306
|
+
background: var(--color-bg-elevated);
|
|
575307
|
+
border: 1px solid var(--color-bg-input);
|
|
575237
575308
|
border-radius: 6px;
|
|
575238
575309
|
padding: 20px;
|
|
575239
575310
|
width: 360px;
|
|
575240
575311
|
max-width: 90vw;
|
|
575241
575312
|
}
|
|
575242
|
-
#key-modal .modal h3 { color:
|
|
575313
|
+
#key-modal .modal h3 { color: var(--color-brand); font-size: 0.8rem; margin-bottom: 12px; }
|
|
575243
575314
|
#key-modal .modal input {
|
|
575244
575315
|
width: 100%;
|
|
575245
|
-
background:
|
|
575246
|
-
border: 1px solid
|
|
575316
|
+
background: var(--color-bg-input);
|
|
575317
|
+
border: 1px solid var(--color-border);
|
|
575247
575318
|
border-radius: 3px;
|
|
575248
575319
|
padding: 8px 10px;
|
|
575249
|
-
color:
|
|
575320
|
+
color: var(--color-fg);
|
|
575250
575321
|
font-family: inherit;
|
|
575251
575322
|
font-size: 0.75rem;
|
|
575252
575323
|
outline: none;
|
|
@@ -575260,9 +575331,9 @@ body {
|
|
|
575260
575331
|
.tab { padding:6px 10px !important; font-size:0.6rem !important; }
|
|
575261
575332
|
}
|
|
575262
575333
|
#key-modal .modal button {
|
|
575263
|
-
background:
|
|
575264
|
-
border: 1px solid
|
|
575265
|
-
color:
|
|
575334
|
+
background: var(--color-bg-input);
|
|
575335
|
+
border: 1px solid var(--color-brand);
|
|
575336
|
+
color: var(--color-brand);
|
|
575266
575337
|
padding: 6px 16px;
|
|
575267
575338
|
border-radius: 3px;
|
|
575268
575339
|
font-family: inherit;
|
|
@@ -575270,14 +575341,812 @@ body {
|
|
|
575270
575341
|
cursor: pointer;
|
|
575271
575342
|
margin-right: 8px;
|
|
575272
575343
|
}
|
|
575344
|
+
|
|
575345
|
+
/* ════════════════════════════════════════════════════════════
|
|
575346
|
+
OWUI-2: Sidebar layout chrome
|
|
575347
|
+
Sidebar-led layout to match openwebui aesthetic.
|
|
575348
|
+
#tabs becomes hidden (still in DOM for legacy switchTab JS).
|
|
575349
|
+
════════════════════════════════════════════════════════════ */
|
|
575350
|
+
body { display:flex; flex-direction:column; height:100vh; margin:0; overflow:hidden; }
|
|
575351
|
+
#oa-shell { font-family: var(--font-ui); }
|
|
575352
|
+
#oa-main { font-family: inherit; }
|
|
575353
|
+
|
|
575354
|
+
/* Hide the legacy top tabs — sidebar replaces them. */
|
|
575355
|
+
#tabs { display:none !important; }
|
|
575356
|
+
|
|
575357
|
+
/* Sidebar nav buttons */
|
|
575358
|
+
.sb-nav {
|
|
575359
|
+
background: transparent;
|
|
575360
|
+
border: 1px solid transparent;
|
|
575361
|
+
color: var(--color-fg-muted);
|
|
575362
|
+
padding: 7px 10px;
|
|
575363
|
+
border-radius: var(--radius-md);
|
|
575364
|
+
cursor: pointer;
|
|
575365
|
+
display: flex;
|
|
575366
|
+
align-items: center;
|
|
575367
|
+
gap: 10px;
|
|
575368
|
+
font-family: var(--font-ui);
|
|
575369
|
+
font-size: 0.78rem;
|
|
575370
|
+
text-align: left;
|
|
575371
|
+
width: 100%;
|
|
575372
|
+
transition: background 0.12s, color 0.12s;
|
|
575373
|
+
}
|
|
575374
|
+
.sb-nav:hover { background: var(--color-bg-hover); color: var(--color-fg); }
|
|
575375
|
+
.sb-nav.active {
|
|
575376
|
+
background: var(--color-bg-hover);
|
|
575377
|
+
color: var(--color-fg);
|
|
575378
|
+
border-color: var(--color-border-strong);
|
|
575379
|
+
}
|
|
575380
|
+
.sb-nav svg { flex-shrink: 0; }
|
|
575381
|
+
|
|
575382
|
+
/* Sidebar chat row */
|
|
575383
|
+
.sb-chat {
|
|
575384
|
+
display: flex;
|
|
575385
|
+
align-items: center;
|
|
575386
|
+
gap: 8px;
|
|
575387
|
+
padding: 6px 10px;
|
|
575388
|
+
border-radius: var(--radius-md);
|
|
575389
|
+
cursor: pointer;
|
|
575390
|
+
color: var(--color-fg-muted);
|
|
575391
|
+
font-size: 0.78rem;
|
|
575392
|
+
white-space: nowrap;
|
|
575393
|
+
overflow: hidden;
|
|
575394
|
+
text-overflow: ellipsis;
|
|
575395
|
+
}
|
|
575396
|
+
.sb-chat:hover { background: var(--color-bg-hover); color: var(--color-fg); }
|
|
575397
|
+
.sb-chat.active { background: var(--color-bg-input); color: var(--color-fg); }
|
|
575398
|
+
|
|
575399
|
+
/* Resize handle hover affordance */
|
|
575400
|
+
#sidebar-resize:hover { background: var(--color-border-strong); }
|
|
575401
|
+
#sidebar-resize.dragging { background: var(--color-accent); }
|
|
575402
|
+
|
|
575403
|
+
/* Collapsed state — only icons visible */
|
|
575404
|
+
#oa-sidebar[data-collapsed="true"] { width: 56px !important; }
|
|
575405
|
+
#oa-sidebar[data-collapsed="true"] .sb-label { display: none !important; }
|
|
575406
|
+
#oa-sidebar[data-collapsed="true"] #sidebar-search-row,
|
|
575407
|
+
#oa-sidebar[data-collapsed="true"] #sidebar-chats,
|
|
575408
|
+
#oa-sidebar[data-collapsed="true"] #sidebar-pinned-section { display: none !important; }
|
|
575409
|
+
#oa-sidebar[data-collapsed="true"] #sidebar-new-chat-row button { padding: 8px; justify-content: center; }
|
|
575410
|
+
#oa-sidebar[data-collapsed="true"] .sb-nav { justify-content: center; padding: 8px; }
|
|
575411
|
+
#oa-sidebar[data-collapsed="true"] #sidebar-footer { flex-direction: column; gap: 6px; padding: 8px 4px; }
|
|
575412
|
+
#oa-sidebar[data-collapsed="true"] #sidebar-status-text { display: none !important; }
|
|
575413
|
+
|
|
575414
|
+
@media (max-width: 768px) {
|
|
575415
|
+
#oa-sidebar { position:fixed; top:0; left:0; bottom:0; z-index:60; box-shadow:4px 0 20px rgba(0,0,0,0.5); }
|
|
575416
|
+
#oa-sidebar[data-collapsed="true"] { transform: translateX(-100%); }
|
|
575417
|
+
}
|
|
575418
|
+
|
|
575419
|
+
/* ════════════════════════════════════════════════════════════
|
|
575420
|
+
OWUI-3: Chat surface — bubbles, streaming indicator,
|
|
575421
|
+
per-message actions, code blocks, system-message chip,
|
|
575422
|
+
tool-call card.
|
|
575423
|
+
════════════════════════════════════════════════════════════ */
|
|
575424
|
+
|
|
575425
|
+
/* Wipe the legacy ::before triangle markers; we use avatars/bubbles. */
|
|
575426
|
+
.msg.user::before,
|
|
575427
|
+
.msg.assistant::before { content: none !important; }
|
|
575428
|
+
|
|
575429
|
+
.msg {
|
|
575430
|
+
position: relative;
|
|
575431
|
+
font-family: var(--font-ui);
|
|
575432
|
+
font-size: 0.88rem;
|
|
575433
|
+
line-height: 1.55;
|
|
575434
|
+
padding: 14px 0;
|
|
575435
|
+
max-width: 100%;
|
|
575436
|
+
}
|
|
575437
|
+
.msg.user {
|
|
575438
|
+
display: flex;
|
|
575439
|
+
justify-content: flex-end;
|
|
575440
|
+
padding: 10px 0;
|
|
575441
|
+
color: var(--color-fg);
|
|
575442
|
+
}
|
|
575443
|
+
.msg.user > * {
|
|
575444
|
+
background: var(--color-bg-input);
|
|
575445
|
+
color: var(--color-fg);
|
|
575446
|
+
padding: 10px 14px;
|
|
575447
|
+
border-radius: 18px;
|
|
575448
|
+
max-width: 78%;
|
|
575449
|
+
white-space: pre-wrap;
|
|
575450
|
+
word-break: break-word;
|
|
575451
|
+
}
|
|
575452
|
+
.msg.assistant {
|
|
575453
|
+
display: block;
|
|
575454
|
+
padding: 14px 0 10px 44px;
|
|
575455
|
+
color: var(--color-fg);
|
|
575456
|
+
}
|
|
575457
|
+
.msg.assistant::before {
|
|
575458
|
+
content: 'OA' !important;
|
|
575459
|
+
position: absolute;
|
|
575460
|
+
top: 14px;
|
|
575461
|
+
left: 0;
|
|
575462
|
+
width: 32px;
|
|
575463
|
+
height: 32px;
|
|
575464
|
+
border-radius: 50%;
|
|
575465
|
+
background: var(--color-bg-input);
|
|
575466
|
+
color: var(--color-brand);
|
|
575467
|
+
display: flex !important;
|
|
575468
|
+
align-items: center;
|
|
575469
|
+
justify-content: center;
|
|
575470
|
+
font-size: 0.7rem;
|
|
575471
|
+
font-weight: 600;
|
|
575472
|
+
font-family: var(--font-ui);
|
|
575473
|
+
letter-spacing: 0.02em;
|
|
575474
|
+
}
|
|
575475
|
+
|
|
575476
|
+
/* System messages collapse into a small centered chip */
|
|
575477
|
+
.msg.system {
|
|
575478
|
+
display: flex;
|
|
575479
|
+
justify-content: center;
|
|
575480
|
+
padding: 6px 0;
|
|
575481
|
+
}
|
|
575482
|
+
.msg.system > * {
|
|
575483
|
+
background: var(--color-bg-elevated);
|
|
575484
|
+
color: var(--color-fg-muted);
|
|
575485
|
+
border: 1px solid var(--color-border);
|
|
575486
|
+
padding: 4px 10px;
|
|
575487
|
+
border-radius: 999px;
|
|
575488
|
+
font-size: 0.7rem;
|
|
575489
|
+
max-width: 70%;
|
|
575490
|
+
white-space: nowrap;
|
|
575491
|
+
overflow: hidden;
|
|
575492
|
+
text-overflow: ellipsis;
|
|
575493
|
+
}
|
|
575494
|
+
|
|
575495
|
+
/* Streaming pulsing-dot indicator */
|
|
575496
|
+
.msg-streaming-indicator {
|
|
575497
|
+
display: inline-flex;
|
|
575498
|
+
align-items: center;
|
|
575499
|
+
gap: 8px;
|
|
575500
|
+
margin: 4px 0 0;
|
|
575501
|
+
color: var(--color-fg-muted);
|
|
575502
|
+
font-size: 0.72rem;
|
|
575503
|
+
}
|
|
575504
|
+
.msg-streaming-indicator .dot {
|
|
575505
|
+
width: 8px;
|
|
575506
|
+
height: 8px;
|
|
575507
|
+
border-radius: 50%;
|
|
575508
|
+
background: var(--color-brand);
|
|
575509
|
+
animation: msg-pulse 1.2s ease-in-out infinite;
|
|
575510
|
+
}
|
|
575511
|
+
@keyframes msg-pulse {
|
|
575512
|
+
0%, 100% { opacity: 0.35; transform: scale(0.85); }
|
|
575513
|
+
50% { opacity: 1; transform: scale(1.1); }
|
|
575514
|
+
}
|
|
575515
|
+
|
|
575516
|
+
/* Per-message actions — visible on hover for assistant messages.
|
|
575517
|
+
The .msg-actions container already exists, but its old style was
|
|
575518
|
+
always-visible micro-buttons. We restyle to openwebui-shape. */
|
|
575519
|
+
.msg-actions {
|
|
575520
|
+
display: flex !important;
|
|
575521
|
+
gap: 4px;
|
|
575522
|
+
margin-top: 8px;
|
|
575523
|
+
opacity: 0;
|
|
575524
|
+
transition: opacity 0.15s;
|
|
575525
|
+
}
|
|
575526
|
+
.msg.assistant:hover .msg-actions,
|
|
575527
|
+
.msg.assistant:focus-within .msg-actions { opacity: 1; }
|
|
575528
|
+
.msg-actions button {
|
|
575529
|
+
background: transparent;
|
|
575530
|
+
border: 1px solid transparent;
|
|
575531
|
+
color: var(--color-fg-muted);
|
|
575532
|
+
font-family: var(--font-ui);
|
|
575533
|
+
font-size: 0.68rem;
|
|
575534
|
+
padding: 3px 8px;
|
|
575535
|
+
border-radius: var(--radius-sm);
|
|
575536
|
+
cursor: pointer;
|
|
575537
|
+
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
|
575538
|
+
}
|
|
575539
|
+
.msg-actions button:hover {
|
|
575540
|
+
background: var(--color-bg-hover);
|
|
575541
|
+
color: var(--color-fg);
|
|
575542
|
+
border-color: var(--color-border);
|
|
575543
|
+
}
|
|
575544
|
+
|
|
575545
|
+
/* Code blocks — language label + copy button, prism-friendly */
|
|
575546
|
+
.msg pre {
|
|
575547
|
+
background: var(--color-bg-elevated);
|
|
575548
|
+
border: 1px solid var(--color-border);
|
|
575549
|
+
border-radius: var(--radius-md);
|
|
575550
|
+
padding: 28px 14px 12px;
|
|
575551
|
+
margin: 8px 0;
|
|
575552
|
+
overflow-x: auto;
|
|
575553
|
+
font-size: 0.78rem;
|
|
575554
|
+
line-height: 1.5;
|
|
575555
|
+
position: relative;
|
|
575556
|
+
font-family: var(--font-mono);
|
|
575557
|
+
}
|
|
575558
|
+
.msg pre code {
|
|
575559
|
+
background: transparent;
|
|
575560
|
+
padding: 0;
|
|
575561
|
+
font-family: var(--font-mono);
|
|
575562
|
+
font-size: inherit;
|
|
575563
|
+
color: var(--color-fg);
|
|
575564
|
+
}
|
|
575565
|
+
.msg pre::before {
|
|
575566
|
+
content: attr(data-lang);
|
|
575567
|
+
position: absolute;
|
|
575568
|
+
top: 6px;
|
|
575569
|
+
left: 12px;
|
|
575570
|
+
font-size: 0.6rem;
|
|
575571
|
+
text-transform: uppercase;
|
|
575572
|
+
letter-spacing: 0.06em;
|
|
575573
|
+
color: var(--color-fg-faint);
|
|
575574
|
+
font-family: var(--font-ui);
|
|
575575
|
+
}
|
|
575576
|
+
.msg pre .copy-btn {
|
|
575577
|
+
background: var(--color-bg-input);
|
|
575578
|
+
border: 1px solid var(--color-border);
|
|
575579
|
+
color: var(--color-fg-muted);
|
|
575580
|
+
font-family: var(--font-ui);
|
|
575581
|
+
padding: 3px 8px;
|
|
575582
|
+
font-size: 0.6rem;
|
|
575583
|
+
border-radius: var(--radius-sm);
|
|
575584
|
+
opacity: 0.6;
|
|
575585
|
+
}
|
|
575586
|
+
.msg pre:hover .copy-btn { opacity: 1; }
|
|
575587
|
+
|
|
575588
|
+
/* Tool-call cards rendered inside an assistant message */
|
|
575589
|
+
.msg .tool-card {
|
|
575590
|
+
display: block;
|
|
575591
|
+
background: var(--color-bg-elevated);
|
|
575592
|
+
border: 1px solid var(--color-border);
|
|
575593
|
+
border-radius: var(--radius-md);
|
|
575594
|
+
padding: 8px 12px;
|
|
575595
|
+
margin: 6px 0;
|
|
575596
|
+
font-size: 0.74rem;
|
|
575597
|
+
color: var(--color-fg-muted);
|
|
575598
|
+
font-family: var(--font-mono);
|
|
575599
|
+
}
|
|
575600
|
+
.msg .tool-card summary {
|
|
575601
|
+
cursor: pointer;
|
|
575602
|
+
color: var(--color-fg);
|
|
575603
|
+
font-family: var(--font-ui);
|
|
575604
|
+
font-size: 0.74rem;
|
|
575605
|
+
list-style: none;
|
|
575606
|
+
}
|
|
575607
|
+
.msg .tool-card summary::-webkit-details-marker { display: none; }
|
|
575608
|
+
.msg .tool-card summary::before {
|
|
575609
|
+
content: '▸';
|
|
575610
|
+
display: inline-block;
|
|
575611
|
+
margin-right: 6px;
|
|
575612
|
+
color: var(--color-fg-muted);
|
|
575613
|
+
transition: transform 0.12s;
|
|
575614
|
+
}
|
|
575615
|
+
.msg .tool-card[open] summary::before { transform: rotate(90deg); }
|
|
575616
|
+
|
|
575617
|
+
/* ════════════════════════════════════════════════════════════
|
|
575618
|
+
OWUI-4: Input toolbar + slash palette + dropzone
|
|
575619
|
+
════════════════════════════════════════════════════════════ */
|
|
575620
|
+
#input-toolbar {
|
|
575621
|
+
display: flex;
|
|
575622
|
+
align-items: center;
|
|
575623
|
+
gap: 4px;
|
|
575624
|
+
padding: 6px 14px 4px;
|
|
575625
|
+
background: var(--color-bg);
|
|
575626
|
+
border-bottom: 1px solid transparent;
|
|
575627
|
+
font-family: var(--font-ui);
|
|
575628
|
+
}
|
|
575629
|
+
#input-toolbar .ibtn {
|
|
575630
|
+
background: transparent;
|
|
575631
|
+
border: 1px solid transparent;
|
|
575632
|
+
color: var(--color-fg-muted);
|
|
575633
|
+
padding: 5px 8px;
|
|
575634
|
+
border-radius: var(--radius-sm);
|
|
575635
|
+
cursor: pointer;
|
|
575636
|
+
display: inline-flex;
|
|
575637
|
+
align-items: center;
|
|
575638
|
+
justify-content: center;
|
|
575639
|
+
font-family: var(--font-ui);
|
|
575640
|
+
font-size: 0.7rem;
|
|
575641
|
+
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
|
575642
|
+
height: 26px;
|
|
575643
|
+
min-width: 26px;
|
|
575644
|
+
}
|
|
575645
|
+
#input-toolbar .ibtn:hover {
|
|
575646
|
+
background: var(--color-bg-hover);
|
|
575647
|
+
color: var(--color-fg);
|
|
575648
|
+
border-color: var(--color-border);
|
|
575649
|
+
}
|
|
575650
|
+
#input-toolbar .ibtn.active {
|
|
575651
|
+
background: var(--color-bg-input);
|
|
575652
|
+
color: var(--color-brand);
|
|
575653
|
+
border-color: var(--color-brand);
|
|
575654
|
+
}
|
|
575655
|
+
#input-toolbar .ibtn-model {
|
|
575656
|
+
background: var(--color-bg-input);
|
|
575657
|
+
border: 1px solid var(--color-border);
|
|
575658
|
+
color: var(--color-fg);
|
|
575659
|
+
font-family: var(--font-ui);
|
|
575660
|
+
font-size: 0.7rem;
|
|
575661
|
+
padding: 3px 6px;
|
|
575662
|
+
border-radius: var(--radius-sm);
|
|
575663
|
+
height: 26px;
|
|
575664
|
+
max-width: 200px;
|
|
575665
|
+
cursor: pointer;
|
|
575666
|
+
outline: none;
|
|
575667
|
+
}
|
|
575668
|
+
|
|
575669
|
+
/* Drag & drop visual on the input row */
|
|
575670
|
+
#input-row.dragover {
|
|
575671
|
+
outline: 2px dashed var(--color-accent);
|
|
575672
|
+
outline-offset: -4px;
|
|
575673
|
+
background: var(--color-bg-elevated);
|
|
575674
|
+
}
|
|
575675
|
+
|
|
575676
|
+
/* Attachment chips row */
|
|
575677
|
+
#attach-chips {
|
|
575678
|
+
display: flex;
|
|
575679
|
+
flex-wrap: wrap;
|
|
575680
|
+
gap: 6px;
|
|
575681
|
+
padding: 4px 14px 2px;
|
|
575682
|
+
background: var(--color-bg);
|
|
575683
|
+
font-family: var(--font-ui);
|
|
575684
|
+
}
|
|
575685
|
+
#attach-chips .chip {
|
|
575686
|
+
display: inline-flex;
|
|
575687
|
+
align-items: center;
|
|
575688
|
+
gap: 4px;
|
|
575689
|
+
background: var(--color-bg-input);
|
|
575690
|
+
border: 1px solid var(--color-border);
|
|
575691
|
+
color: var(--color-fg);
|
|
575692
|
+
font-size: 0.7rem;
|
|
575693
|
+
padding: 2px 6px;
|
|
575694
|
+
border-radius: 999px;
|
|
575695
|
+
max-width: 220px;
|
|
575696
|
+
white-space: nowrap;
|
|
575697
|
+
overflow: hidden;
|
|
575698
|
+
}
|
|
575699
|
+
#attach-chips .chip .name {
|
|
575700
|
+
white-space: nowrap;
|
|
575701
|
+
overflow: hidden;
|
|
575702
|
+
text-overflow: ellipsis;
|
|
575703
|
+
max-width: 160px;
|
|
575704
|
+
}
|
|
575705
|
+
#attach-chips .chip .x {
|
|
575706
|
+
cursor: pointer;
|
|
575707
|
+
color: var(--color-fg-muted);
|
|
575708
|
+
margin-left: 2px;
|
|
575709
|
+
border: none;
|
|
575710
|
+
background: transparent;
|
|
575711
|
+
padding: 0 2px;
|
|
575712
|
+
font-size: 0.85rem;
|
|
575713
|
+
line-height: 1;
|
|
575714
|
+
}
|
|
575715
|
+
#attach-chips .chip .x:hover { color: var(--color-error); }
|
|
575716
|
+
|
|
575717
|
+
/* Slash command palette */
|
|
575718
|
+
#slash-palette {
|
|
575719
|
+
position: absolute;
|
|
575720
|
+
bottom: 100%;
|
|
575721
|
+
left: 14px;
|
|
575722
|
+
right: 14px;
|
|
575723
|
+
margin-bottom: 6px;
|
|
575724
|
+
max-height: 240px;
|
|
575725
|
+
overflow-y: auto;
|
|
575726
|
+
background: var(--color-bg-elevated);
|
|
575727
|
+
border: 1px solid var(--color-border);
|
|
575728
|
+
border-radius: var(--radius-md);
|
|
575729
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
|
|
575730
|
+
z-index: 30;
|
|
575731
|
+
font-family: var(--font-ui);
|
|
575732
|
+
}
|
|
575733
|
+
#slash-palette .slash-row {
|
|
575734
|
+
padding: 8px 12px;
|
|
575735
|
+
font-size: 0.78rem;
|
|
575736
|
+
cursor: pointer;
|
|
575737
|
+
display: flex;
|
|
575738
|
+
align-items: center;
|
|
575739
|
+
gap: 10px;
|
|
575740
|
+
color: var(--color-fg);
|
|
575741
|
+
border-bottom: 1px solid var(--color-bg);
|
|
575742
|
+
}
|
|
575743
|
+
#slash-palette .slash-row:last-child { border-bottom: none; }
|
|
575744
|
+
#slash-palette .slash-row:hover,
|
|
575745
|
+
#slash-palette .slash-row.selected {
|
|
575746
|
+
background: var(--color-bg-hover);
|
|
575747
|
+
}
|
|
575748
|
+
#slash-palette .slash-name {
|
|
575749
|
+
font-family: var(--font-mono);
|
|
575750
|
+
color: var(--color-brand);
|
|
575751
|
+
font-size: 0.74rem;
|
|
575752
|
+
flex-shrink: 0;
|
|
575753
|
+
}
|
|
575754
|
+
#slash-palette .slash-desc {
|
|
575755
|
+
color: var(--color-fg-muted);
|
|
575756
|
+
font-size: 0.7rem;
|
|
575757
|
+
white-space: nowrap;
|
|
575758
|
+
overflow: hidden;
|
|
575759
|
+
text-overflow: ellipsis;
|
|
575760
|
+
}
|
|
575761
|
+
|
|
575762
|
+
/* Make sure #footer is the slash palette anchor (relative). */
|
|
575763
|
+
#footer { position: relative; }
|
|
575764
|
+
|
|
575765
|
+
/* Bigger textarea growth budget — was 120px (~6 lines), now 240px. */
|
|
575766
|
+
#input-area { max-height: 240px !important; }
|
|
575767
|
+
|
|
575768
|
+
/* ════════════════════════════════════════════════════════════
|
|
575769
|
+
OWUI-5: Modal scaffold — full-screen overlay with left-rail
|
|
575770
|
+
tabs + content pane + footer.
|
|
575771
|
+
════════════════════════════════════════════════════════════ */
|
|
575772
|
+
.oa-modal {
|
|
575773
|
+
position: fixed;
|
|
575774
|
+
inset: 0;
|
|
575775
|
+
display: flex;
|
|
575776
|
+
align-items: center;
|
|
575777
|
+
justify-content: center;
|
|
575778
|
+
z-index: 100;
|
|
575779
|
+
font-family: var(--font-ui);
|
|
575780
|
+
}
|
|
575781
|
+
.oa-modal[style*="display:none"] { display: none !important; }
|
|
575782
|
+
.oa-modal-backdrop {
|
|
575783
|
+
position: absolute;
|
|
575784
|
+
inset: 0;
|
|
575785
|
+
background: rgba(0, 0, 0, 0.6);
|
|
575786
|
+
backdrop-filter: blur(2px);
|
|
575787
|
+
}
|
|
575788
|
+
.oa-modal-window {
|
|
575789
|
+
position: relative;
|
|
575790
|
+
width: min(90vw, 920px);
|
|
575791
|
+
height: min(86vh, 640px);
|
|
575792
|
+
background: var(--color-bg);
|
|
575793
|
+
border: 1px solid var(--color-border);
|
|
575794
|
+
border-radius: var(--radius-lg);
|
|
575795
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
|
575796
|
+
display: flex;
|
|
575797
|
+
flex-direction: column;
|
|
575798
|
+
overflow: hidden;
|
|
575799
|
+
}
|
|
575800
|
+
.oa-modal-header {
|
|
575801
|
+
padding: 16px 20px;
|
|
575802
|
+
border-bottom: 1px solid var(--color-border);
|
|
575803
|
+
display: flex;
|
|
575804
|
+
align-items: center;
|
|
575805
|
+
justify-content: space-between;
|
|
575806
|
+
}
|
|
575807
|
+
.oa-modal-header h3 {
|
|
575808
|
+
margin: 0;
|
|
575809
|
+
font-size: 0.98rem;
|
|
575810
|
+
font-weight: 600;
|
|
575811
|
+
color: var(--color-fg);
|
|
575812
|
+
letter-spacing: -0.01em;
|
|
575813
|
+
}
|
|
575814
|
+
.oa-modal-close {
|
|
575815
|
+
background: transparent;
|
|
575816
|
+
border: none;
|
|
575817
|
+
color: var(--color-fg-muted);
|
|
575818
|
+
font-size: 1.4rem;
|
|
575819
|
+
line-height: 1;
|
|
575820
|
+
cursor: pointer;
|
|
575821
|
+
padding: 4px 10px;
|
|
575822
|
+
border-radius: var(--radius-sm);
|
|
575823
|
+
}
|
|
575824
|
+
.oa-modal-close:hover { background: var(--color-bg-hover); color: var(--color-fg); }
|
|
575825
|
+
.oa-modal-body {
|
|
575826
|
+
display: grid;
|
|
575827
|
+
grid-template-columns: 200px 1fr;
|
|
575828
|
+
flex: 1;
|
|
575829
|
+
min-height: 0;
|
|
575830
|
+
}
|
|
575831
|
+
.oa-modal-rail {
|
|
575832
|
+
border-right: 1px solid var(--color-border);
|
|
575833
|
+
padding: 12px 8px;
|
|
575834
|
+
display: flex;
|
|
575835
|
+
flex-direction: column;
|
|
575836
|
+
gap: 2px;
|
|
575837
|
+
background: var(--color-bg-elevated);
|
|
575838
|
+
overflow-y: auto;
|
|
575839
|
+
}
|
|
575840
|
+
.rail-tab {
|
|
575841
|
+
background: transparent;
|
|
575842
|
+
border: 1px solid transparent;
|
|
575843
|
+
color: var(--color-fg-muted);
|
|
575844
|
+
text-align: left;
|
|
575845
|
+
padding: 8px 12px;
|
|
575846
|
+
border-radius: var(--radius-md);
|
|
575847
|
+
cursor: pointer;
|
|
575848
|
+
font-size: 0.8rem;
|
|
575849
|
+
font-family: var(--font-ui);
|
|
575850
|
+
transition: background 0.12s, color 0.12s;
|
|
575851
|
+
}
|
|
575852
|
+
.rail-tab:hover { background: var(--color-bg-hover); color: var(--color-fg); }
|
|
575853
|
+
.rail-tab.active {
|
|
575854
|
+
background: var(--color-bg-input);
|
|
575855
|
+
color: var(--color-fg);
|
|
575856
|
+
border-color: var(--color-border-strong);
|
|
575857
|
+
}
|
|
575858
|
+
.oa-modal-pane {
|
|
575859
|
+
padding: 18px 22px;
|
|
575860
|
+
overflow-y: auto;
|
|
575861
|
+
color: var(--color-fg);
|
|
575862
|
+
font-size: 0.82rem;
|
|
575863
|
+
}
|
|
575864
|
+
.oa-modal-pane h4 {
|
|
575865
|
+
margin: 0 0 6px;
|
|
575866
|
+
color: var(--color-fg);
|
|
575867
|
+
font-size: 0.86rem;
|
|
575868
|
+
}
|
|
575869
|
+
.oa-modal-aside {
|
|
575870
|
+
border-left: 1px solid var(--color-border);
|
|
575871
|
+
background: var(--color-bg-elevated);
|
|
575872
|
+
overflow-y: auto;
|
|
575873
|
+
}
|
|
575874
|
+
.settings-pane[hidden] { display: none !important; }
|
|
575875
|
+
.settings-pane.active { display: block; }
|
|
575876
|
+
.oa-modal-footer {
|
|
575877
|
+
padding: 12px 20px;
|
|
575878
|
+
border-top: 1px solid var(--color-border);
|
|
575879
|
+
display: flex;
|
|
575880
|
+
justify-content: flex-end;
|
|
575881
|
+
gap: 8px;
|
|
575882
|
+
background: var(--color-bg-elevated);
|
|
575883
|
+
}
|
|
575884
|
+
.oa-btn-secondary {
|
|
575885
|
+
background: var(--color-bg-input);
|
|
575886
|
+
border: 1px solid var(--color-border);
|
|
575887
|
+
color: var(--color-fg);
|
|
575888
|
+
font-family: var(--font-ui);
|
|
575889
|
+
font-size: 0.78rem;
|
|
575890
|
+
padding: 6px 14px;
|
|
575891
|
+
border-radius: var(--radius-sm);
|
|
575892
|
+
cursor: pointer;
|
|
575893
|
+
}
|
|
575894
|
+
.oa-btn-secondary:hover {
|
|
575895
|
+
background: var(--color-bg-hover);
|
|
575896
|
+
border-color: var(--color-border-strong);
|
|
575897
|
+
}
|
|
575898
|
+
|
|
575899
|
+
/* Settings models list rows */
|
|
575900
|
+
#settings-models-list .row,
|
|
575901
|
+
#mm-list .row {
|
|
575902
|
+
display: flex;
|
|
575903
|
+
align-items: center;
|
|
575904
|
+
justify-content: space-between;
|
|
575905
|
+
padding: 8px 12px;
|
|
575906
|
+
border: 1px solid transparent;
|
|
575907
|
+
border-radius: var(--radius-sm);
|
|
575908
|
+
cursor: pointer;
|
|
575909
|
+
font-size: 0.8rem;
|
|
575910
|
+
margin: 2px 0;
|
|
575911
|
+
}
|
|
575912
|
+
#settings-models-list .row:hover,
|
|
575913
|
+
#mm-list .row:hover,
|
|
575914
|
+
#mm-list .row.selected {
|
|
575915
|
+
background: var(--color-bg-hover);
|
|
575916
|
+
border-color: var(--color-border);
|
|
575917
|
+
}
|
|
575918
|
+
#settings-models-list .row.active {
|
|
575919
|
+
border-color: var(--color-brand);
|
|
575920
|
+
background: var(--color-bg-input);
|
|
575921
|
+
color: var(--color-brand);
|
|
575922
|
+
}
|
|
575923
|
+
#settings-models-list .row .right,
|
|
575924
|
+
#mm-list .row .right {
|
|
575925
|
+
font-size: 0.7rem;
|
|
575926
|
+
color: var(--color-fg-faint);
|
|
575927
|
+
}
|
|
575928
|
+
|
|
575929
|
+
/* ════════════════════════════════════════════════════════════
|
|
575930
|
+
OWUI-6: Folders + chat-row layout + context menu
|
|
575931
|
+
════════════════════════════════════════════════════════════ */
|
|
575932
|
+
.sb-chat {
|
|
575933
|
+
display: flex !important;
|
|
575934
|
+
align-items: center;
|
|
575935
|
+
}
|
|
575936
|
+
.sb-chat-title {
|
|
575937
|
+
flex: 1;
|
|
575938
|
+
white-space: nowrap;
|
|
575939
|
+
overflow: hidden;
|
|
575940
|
+
text-overflow: ellipsis;
|
|
575941
|
+
}
|
|
575942
|
+
.sb-chat-menu,
|
|
575943
|
+
.sb-folder-menu {
|
|
575944
|
+
background: transparent;
|
|
575945
|
+
border: none;
|
|
575946
|
+
color: var(--color-fg-faint);
|
|
575947
|
+
cursor: pointer;
|
|
575948
|
+
padding: 0 4px;
|
|
575949
|
+
border-radius: var(--radius-sm);
|
|
575950
|
+
font-family: var(--font-ui);
|
|
575951
|
+
font-size: 0.85rem;
|
|
575952
|
+
opacity: 0;
|
|
575953
|
+
transition: opacity 0.12s, background 0.12s, color 0.12s;
|
|
575954
|
+
}
|
|
575955
|
+
.sb-chat:hover .sb-chat-menu,
|
|
575956
|
+
.sb-folder-row:hover .sb-folder-menu { opacity: 1; }
|
|
575957
|
+
.sb-chat-menu:hover,
|
|
575958
|
+
.sb-folder-menu:hover { background: var(--color-bg-input); color: var(--color-fg); }
|
|
575959
|
+
|
|
575960
|
+
.sb-folder-action {
|
|
575961
|
+
background: transparent;
|
|
575962
|
+
border: 1px solid transparent;
|
|
575963
|
+
color: var(--color-fg-faint);
|
|
575964
|
+
padding: 2px 6px;
|
|
575965
|
+
border-radius: var(--radius-sm);
|
|
575966
|
+
cursor: pointer;
|
|
575967
|
+
font-size: 0.62rem;
|
|
575968
|
+
font-family: var(--font-ui);
|
|
575969
|
+
text-transform: none;
|
|
575970
|
+
letter-spacing: 0;
|
|
575971
|
+
}
|
|
575972
|
+
.sb-folder-action:hover { background: var(--color-bg-hover); color: var(--color-fg); border-color: var(--color-border); }
|
|
575973
|
+
|
|
575974
|
+
.sb-folder { margin: 4px 0; }
|
|
575975
|
+
.sb-folder.drop-target {
|
|
575976
|
+
outline: 1px dashed var(--color-accent);
|
|
575977
|
+
border-radius: var(--radius-sm);
|
|
575978
|
+
}
|
|
575979
|
+
.sb-folder-row {
|
|
575980
|
+
display: flex;
|
|
575981
|
+
align-items: center;
|
|
575982
|
+
gap: 6px;
|
|
575983
|
+
padding: 6px 6px;
|
|
575984
|
+
cursor: pointer;
|
|
575985
|
+
color: var(--color-fg-muted);
|
|
575986
|
+
border-radius: var(--radius-sm);
|
|
575987
|
+
}
|
|
575988
|
+
.sb-folder-row:hover { background: var(--color-bg-hover); color: var(--color-fg); }
|
|
575989
|
+
.sb-folder-caret {
|
|
575990
|
+
width: 10px;
|
|
575991
|
+
display: inline-block;
|
|
575992
|
+
text-align: center;
|
|
575993
|
+
font-size: 0.75rem;
|
|
575994
|
+
color: var(--color-fg-muted);
|
|
575995
|
+
}
|
|
575996
|
+
.sb-folder-name { flex: 1; font-size: 0.78rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
575997
|
+
.sb-folder-count {
|
|
575998
|
+
font-size: 0.6rem;
|
|
575999
|
+
color: var(--color-fg-faint);
|
|
576000
|
+
background: var(--color-bg-input);
|
|
576001
|
+
padding: 1px 6px;
|
|
576002
|
+
border-radius: 999px;
|
|
576003
|
+
}
|
|
576004
|
+
.sb-folder-body {
|
|
576005
|
+
padding: 0 0 0 14px;
|
|
576006
|
+
border-left: 1px solid var(--color-border);
|
|
576007
|
+
margin-left: 8px;
|
|
576008
|
+
}
|
|
576009
|
+
.sb-folder-empty {
|
|
576010
|
+
padding: 6px 8px;
|
|
576011
|
+
color: var(--color-fg-faint);
|
|
576012
|
+
font-size: 0.65rem;
|
|
576013
|
+
}
|
|
576014
|
+
|
|
576015
|
+
/* Context menu shared by chat + folder row ⋮ */
|
|
576016
|
+
#sb-chat-menu .sb-menu-item {
|
|
576017
|
+
display: block;
|
|
576018
|
+
width: 100%;
|
|
576019
|
+
text-align: left;
|
|
576020
|
+
background: transparent;
|
|
576021
|
+
border: none;
|
|
576022
|
+
color: var(--color-fg);
|
|
576023
|
+
padding: 7px 12px;
|
|
576024
|
+
font-family: var(--font-ui);
|
|
576025
|
+
font-size: 0.78rem;
|
|
576026
|
+
cursor: pointer;
|
|
576027
|
+
}
|
|
576028
|
+
#sb-chat-menu .sb-menu-item:hover { background: var(--color-bg-hover); }
|
|
576029
|
+
#sb-chat-menu .sb-menu-item.sub { padding-left: 22px; color: var(--color-fg-muted); font-size: 0.74rem; }
|
|
576030
|
+
#sb-chat-menu .sb-menu-item.danger { color: var(--color-error); }
|
|
576031
|
+
#sb-chat-menu .sb-menu-item.danger:hover { background: var(--color-bg-hover); color: var(--color-error); }
|
|
576032
|
+
#sb-chat-menu .sb-menu-sub {
|
|
576033
|
+
padding: 6px 12px 2px;
|
|
576034
|
+
color: var(--color-fg-faint);
|
|
576035
|
+
font-size: 0.62rem;
|
|
576036
|
+
text-transform: uppercase;
|
|
576037
|
+
letter-spacing: 0.05em;
|
|
576038
|
+
}
|
|
575273
576039
|
</style>
|
|
575274
576040
|
</head>
|
|
575275
576041
|
<body>
|
|
575276
576042
|
|
|
576043
|
+
<!-- ════════════════════════════════════════════════════════════════
|
|
576044
|
+
OWUI-2: Open WebUI-shaped sidebar layout
|
|
576045
|
+
────────────────────────────────────────────────────────────────
|
|
576046
|
+
Top-level structure: <aside id="oa-sidebar"> + <div id="oa-main">
|
|
576047
|
+
Sidebar holds: brand + new-chat, search, recents (pinned + recent),
|
|
576048
|
+
and a footer rail with tab links + settings/key buttons.
|
|
576049
|
+
The existing tab-bar moves to be HIDDEN (kept in DOM so legacy JS
|
|
576050
|
+
works); switchTab is driven by sidebar nav buttons instead.
|
|
576051
|
+
Resize handle at the right edge updates $sidebarWidth.
|
|
576052
|
+
════════════════════════════════════════════════════════════════ -->
|
|
576053
|
+
<div id="oa-shell" style="display:flex;flex:1;overflow:hidden;height:100vh">
|
|
576054
|
+
|
|
576055
|
+
<aside id="oa-sidebar" data-collapsed="false" style="width:260px;flex-shrink:0;background:var(--color-bg-elevated);border-right:1px solid var(--color-border);display:flex;flex-direction:column;position:relative;transition:width 0.18s ease">
|
|
576056
|
+
<!-- Brand + collapse toggle -->
|
|
576057
|
+
<div style="padding:14px 12px 8px;display:flex;align-items:center;gap:10px">
|
|
576058
|
+
<button id="sidebar-toggle" onclick="toggleSidebar()" title="Toggle sidebar (Cmd/Ctrl+B)" style="background:transparent;border:none;color:var(--color-fg-muted);padding:4px;border-radius:var(--radius-sm);cursor:pointer;display:flex;align-items:center;justify-content:center;width:28px;height:28px">
|
|
576059
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
|
|
576060
|
+
</button>
|
|
576061
|
+
<span id="sidebar-brand" style="font-weight:600;font-size:0.92rem;color:var(--color-fg);letter-spacing:-0.01em;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">Open Agents</span>
|
|
576062
|
+
</div>
|
|
576063
|
+
|
|
576064
|
+
<!-- New chat button -->
|
|
576065
|
+
<div id="sidebar-new-chat-row" style="padding:0 12px 10px">
|
|
576066
|
+
<button onclick="newChatSession(); switchTab('chat')" title="New chat (Cmd/Ctrl+Shift+O)" style="width:100%;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:8px 12px;border-radius:var(--radius-md);cursor:pointer;display:flex;align-items:center;gap:10px;font-size:0.82rem;font-weight:500;transition:background 0.12s">
|
|
576067
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
|
|
576068
|
+
<span class="sb-label">New chat</span>
|
|
576069
|
+
</button>
|
|
576070
|
+
</div>
|
|
576071
|
+
|
|
576072
|
+
<!-- Search bar -->
|
|
576073
|
+
<div id="sidebar-search-row" style="padding:0 12px 8px">
|
|
576074
|
+
<div style="position:relative">
|
|
576075
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="position:absolute;left:10px;top:50%;transform:translateY(-50%);color:var(--color-fg-faint);pointer-events:none"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
576076
|
+
<input id="sidebar-search" placeholder="Search chats..." class="sb-label" oninput="filterSidebarChats(this.value)" style="width:100%;background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-fg);padding:6px 10px 6px 32px;border-radius:var(--radius-md);font-family:var(--font-ui);font-size:0.78rem;outline:none">
|
|
576077
|
+
</div>
|
|
576078
|
+
</div>
|
|
576079
|
+
|
|
576080
|
+
<!-- Chats scroll region — pinned + folders + recent -->
|
|
576081
|
+
<nav id="sidebar-chats" class="sb-label" style="flex:1;overflow-y:auto;padding:4px 8px 8px">
|
|
576082
|
+
<div id="sidebar-pinned-section" style="display:none">
|
|
576083
|
+
<div style="padding:8px 8px 4px;font-size:0.62rem;color:var(--color-fg-faint);text-transform:uppercase;letter-spacing:0.06em">Pinned</div>
|
|
576084
|
+
<div id="sidebar-pinned-list"></div>
|
|
576085
|
+
</div>
|
|
576086
|
+
<div style="padding:8px 8px 4px;font-size:0.62rem;color:var(--color-fg-faint);text-transform:uppercase;letter-spacing:0.06em;display:flex;align-items:center;gap:6px">
|
|
576087
|
+
<span style="flex:1">Chats</span>
|
|
576088
|
+
<span id="sidebar-folder-tools"></span>
|
|
576089
|
+
</div>
|
|
576090
|
+
<div id="sidebar-recent-list">
|
|
576091
|
+
<div style="padding:14px 8px;color:var(--color-fg-faint);font-size:0.7rem;text-align:center">No chats yet</div>
|
|
576092
|
+
</div>
|
|
576093
|
+
</nav>
|
|
576094
|
+
|
|
576095
|
+
<!-- Section nav (the legacy tabs migrated into the sidebar footer) -->
|
|
576096
|
+
<div id="sidebar-nav" class="sb-label" style="border-top:1px solid var(--color-border);padding:8px;display:flex;flex-direction:column;gap:2px">
|
|
576097
|
+
<button class="sb-nav" data-tab="chat" onclick="switchTab('chat')" title="Chat">
|
|
576098
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
576099
|
+
<span class="sb-label">Chat</span>
|
|
576100
|
+
</button>
|
|
576101
|
+
<button class="sb-nav" data-tab="agent" onclick="switchTab('agent')" title="Agent">
|
|
576102
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 4 7v10l8 5 8-5V7l-8-5z"/><path d="M12 22V12"/><path d="m4 7 8 5 8-5"/></svg>
|
|
576103
|
+
<span class="sb-label">Agent</span>
|
|
576104
|
+
</button>
|
|
576105
|
+
<button class="sb-nav" data-tab="voice" onclick="switchTab('voice')" title="Voice">
|
|
576106
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
|
|
576107
|
+
<span class="sb-label">Voice</span>
|
|
576108
|
+
</button>
|
|
576109
|
+
<button class="sb-nav" data-tab="projects" onclick="switchTab('projects')" title="Projects">
|
|
576110
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
|
576111
|
+
<span class="sb-label">Projects</span>
|
|
576112
|
+
</button>
|
|
576113
|
+
<button class="sb-nav" data-tab="jobs" onclick="switchTab('jobs')" title="Dashboard">
|
|
576114
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>
|
|
576115
|
+
<span class="sb-label">Dashboard</span>
|
|
576116
|
+
</button>
|
|
576117
|
+
<button class="sb-nav" data-tab="activity" onclick="switchTab('activity')" title="Activity">
|
|
576118
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
|
576119
|
+
<span class="sb-label">Activity</span>
|
|
576120
|
+
</button>
|
|
576121
|
+
<button class="sb-nav" data-tab="config" onclick="switchTab('config')" title="Config">
|
|
576122
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
576123
|
+
<span class="sb-label">Settings</span>
|
|
576124
|
+
</button>
|
|
576125
|
+
</div>
|
|
576126
|
+
|
|
576127
|
+
<!-- Sidebar footer — connection + key + update -->
|
|
576128
|
+
<div id="sidebar-footer" class="sb-label" style="border-top:1px solid var(--color-border);padding:10px 12px;display:flex;align-items:center;gap:8px;font-size:0.65rem;color:var(--color-fg-muted)">
|
|
576129
|
+
<span id="sidebar-status-dot" style="width:8px;height:8px;border-radius:50%;background:var(--color-fg-faint);flex-shrink:0" title="Backend connection"></span>
|
|
576130
|
+
<span id="sidebar-status-text" style="flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">connecting...</span>
|
|
576131
|
+
<button id="sidebar-update-btn" onclick="doUpdate()" style="display:none;background:var(--color-warning);border:none;color:#000;padding:2px 6px;border-radius:var(--radius-sm);font-size:0.6rem;cursor:pointer;font-weight:600">update</button>
|
|
576132
|
+
<button id="sidebar-key-btn" onclick="document.getElementById('key-modal').style.display='flex'" title="Set API key" style="background:transparent;border:1px solid var(--color-border);color:var(--color-fg-muted);padding:2px 6px;border-radius:var(--radius-sm);font-size:0.6rem;cursor:pointer">key</button>
|
|
576133
|
+
</div>
|
|
576134
|
+
|
|
576135
|
+
<!-- Resize handle -->
|
|
576136
|
+
<div id="sidebar-resize" title="Drag to resize sidebar" style="position:absolute;top:0;right:-3px;width:6px;height:100%;cursor:col-resize;z-index:10"></div>
|
|
576137
|
+
</aside>
|
|
576138
|
+
|
|
576139
|
+
<!-- Main pane — wraps the existing header + tabs + panels.
|
|
576140
|
+
We keep the existing #header / #tabs / panels structure intact so
|
|
576141
|
+
legacy JS keeps working; the sidebar simply becomes the primary
|
|
576142
|
+
navigation surface and the old tabs get visually de-emphasized
|
|
576143
|
+
(still in DOM, hidden via display:none below — see #tabs override). -->
|
|
576144
|
+
<div id="oa-main" style="flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden;background:var(--color-bg)">
|
|
576145
|
+
|
|
575277
576146
|
<div id="header">
|
|
575278
576147
|
<span class="accent">OA</span>
|
|
575279
576148
|
<span class="status" id="status">connecting...</span>
|
|
575280
|
-
<span id="update-btn" style="display:none;background:#3a2a10;border:1px solid
|
|
576149
|
+
<span id="update-btn" style="display:none;background:#3a2a10;border:1px solid var(--color-brand);color:var(--color-brand);padding:2px 8px;border-radius:3px;font-family:inherit;font-size:0.6rem;cursor:pointer" onclick="doUpdate()">update</span>
|
|
575281
576150
|
<select id="model-select"><option>loading...</option></select>
|
|
575282
576151
|
<button class="key-btn" id="files-btn" onclick="toggleFilesForActiveTab()" title="Toggle workspace sidebar (chat or agent depending on active tab)">files</button>
|
|
575283
576152
|
<button class="key-btn" id="sandbox-toggle" onclick="toggleSandbox()" title="Toggle Docker sandbox" style="opacity:0.5">sandbox: off</button>
|
|
@@ -575292,40 +576161,40 @@ body {
|
|
|
575292
576161
|
together when wrapping: (1) tab buttons, (2) session selector,
|
|
575293
576162
|
(3) sys metrics + token counter. On narrow viewports the sections
|
|
575294
576163
|
flow onto multiple rows instead of overflowing the header. -->
|
|
575295
|
-
<div id="tabs" style="display:flex;flex-wrap:wrap;gap:0.25rem;background
|
|
576164
|
+
<div id="tabs" style="display:flex;flex-wrap:wrap;gap:0.25rem;background:var(--color-bg-elevated);border-bottom:1px solid var(--color-bg-input);padding:0 16px;flex-shrink:0">
|
|
575296
576165
|
<!-- Section 1: tab buttons -->
|
|
575297
576166
|
<div id="tabs-section-buttons" style="display:flex;gap:0;flex-shrink:0">
|
|
575298
|
-
<button class="tab" onclick="switchTab('projects')" id="tab-projects" style="background:none;border:none;border-bottom:2px solid transparent;color
|
|
575299
|
-
<button class="tab active" onclick="switchTab('chat')" id="tab-chat" style="background:none;border:none;border-bottom:2px solid
|
|
575300
|
-
<button class="tab" onclick="switchTab('agent')" id="tab-agent" style="background:none;border:none;border-bottom:2px solid transparent;color
|
|
575301
|
-
<button class="tab" onclick="switchTab('jobs')" id="tab-jobs" style="background:none;border:none;border-bottom:2px solid transparent;color
|
|
575302
|
-
<button class="tab" onclick="switchTab('config')" id="tab-config" style="background:none;border:none;border-bottom:2px solid transparent;color
|
|
575303
|
-
<button class="tab" onclick="switchTab('activity')" id="tab-activity" style="background:none;border:none;border-bottom:2px solid transparent;color
|
|
575304
|
-
<button class="tab" onclick="switchTab('voice')" id="tab-voice" style="background:none;border:none;border-bottom:2px solid transparent;color
|
|
576167
|
+
<button class="tab" onclick="switchTab('projects')" id="tab-projects" style="background:none;border:none;border-bottom:2px solid transparent;color:var(--color-fg-faint);padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">projects</button>
|
|
576168
|
+
<button class="tab active" onclick="switchTab('chat')" id="tab-chat" style="background:none;border:none;border-bottom:2px solid var(--color-brand);color:var(--color-brand);padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">chat</button>
|
|
576169
|
+
<button class="tab" onclick="switchTab('agent')" id="tab-agent" style="background:none;border:none;border-bottom:2px solid transparent;color:var(--color-fg-faint);padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">agent</button>
|
|
576170
|
+
<button class="tab" onclick="switchTab('jobs')" id="tab-jobs" style="background:none;border:none;border-bottom:2px solid transparent;color:var(--color-fg-faint);padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">dashboard</button>
|
|
576171
|
+
<button class="tab" onclick="switchTab('config')" id="tab-config" style="background:none;border:none;border-bottom:2px solid transparent;color:var(--color-fg-faint);padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">config</button>
|
|
576172
|
+
<button class="tab" onclick="switchTab('activity')" id="tab-activity" style="background:none;border:none;border-bottom:2px solid transparent;color:var(--color-fg-faint);padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">activity</button>
|
|
576173
|
+
<button class="tab" onclick="switchTab('voice')" id="tab-voice" style="background:none;border:none;border-bottom:2px solid transparent;color:var(--color-fg-faint);padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">voice</button>
|
|
575305
576174
|
</div>
|
|
575306
576175
|
<!-- Section 2: session dropdown (legacy slot — the per-tab dropdowns
|
|
575307
576176
|
now live in #session-topbar above the active panel; this slot is
|
|
575308
576177
|
kept hidden so existing JS that touches #session-select still works
|
|
575309
576178
|
without forcing a switch). -->
|
|
575310
576179
|
<div id="tabs-section-sessions" style="display:none;gap:6px;align-items:center;flex-shrink:0">
|
|
575311
|
-
<select id="session-select" onchange="switchSession(this.value)" style="background
|
|
576180
|
+
<select id="session-select" onchange="switchSession(this.value)" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:2px 6px;border-radius:3px;font-family:inherit;font-size:0.6rem;max-width:160px" title="Chat sessions">
|
|
575312
576181
|
<option value="">new session</option>
|
|
575313
576182
|
</select>
|
|
575314
576183
|
</div>
|
|
575315
576184
|
<!-- Section 3: sys metrics + token counter -->
|
|
575316
576185
|
<div id="tabs-section-metrics" style="display:flex;flex-flow:wrap;align-items:center;gap:10px;flex-shrink:0">
|
|
575317
|
-
<span id="sys-metrics" style="font-size:0.6rem;color
|
|
575318
|
-
<span id="token-counter" style="font-size:0.6rem;color
|
|
576186
|
+
<span id="sys-metrics" style="font-size:0.6rem;color:var(--color-fg-faint);display:flex;flex-flow:column;justify-content:center"></span>
|
|
576187
|
+
<span id="token-counter" style="font-size:0.6rem;color:var(--color-fg-faint);display:flex;flex-flow:column;justify-content:center;padding:0 6px">0 tokens</span>
|
|
575319
576188
|
</div>
|
|
575320
576189
|
</div>
|
|
575321
576190
|
<div id="chat-container" style="display:flex;flex:1;overflow:hidden">
|
|
575322
|
-
<div id="workspace-sidebar" style="display:none;width:250px;background
|
|
576191
|
+
<div id="workspace-sidebar" style="display:none;width:250px;background:var(--color-bg-elevated);border-right:1px solid var(--color-bg-input);overflow-y:auto;padding:8px;flex-shrink:0;font-size:0.7rem">
|
|
575323
576192
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
575324
|
-
<span style="color
|
|
575325
|
-
<button onclick="toggleWorkspace()" style="background:none;border:none;color
|
|
576193
|
+
<span style="color:var(--color-brand);font-size:0.7rem;font-weight:bold">Workspace</span>
|
|
576194
|
+
<button onclick="toggleWorkspace()" style="background:none;border:none;color:var(--color-fg-faint);cursor:pointer;font-size:0.8rem">x</button>
|
|
575326
576195
|
</div>
|
|
575327
|
-
<div id="workspace-cwd" style="color
|
|
575328
|
-
<div id="workspace-tree" style="color
|
|
576196
|
+
<div id="workspace-cwd" style="color:var(--color-fg-faint);font-size:0.6rem;margin-bottom:8px;word-break:break-all"></div>
|
|
576197
|
+
<div id="workspace-tree" style="color:var(--color-fg)"></div>
|
|
575329
576198
|
</div>
|
|
575330
576199
|
<!-- Vertical column: session topbar + checklist + conversation -->
|
|
575331
576200
|
<div style="display:flex;flex-direction:column;flex:1;min-width:0;overflow:hidden">
|
|
@@ -575336,11 +576205,11 @@ body {
|
|
|
575336
576205
|
<option value="">+ new session</option>
|
|
575337
576206
|
</select>
|
|
575338
576207
|
<button onclick="newChatSession()" title="Start a fresh chat session">new</button>
|
|
575339
|
-
<button onclick="deleteChatSession()" title="Delete the current chat session" style="color
|
|
576208
|
+
<button onclick="deleteChatSession()" title="Delete the current chat session" style="color:var(--color-error);border-color:var(--color-error)">del</button>
|
|
575340
576209
|
</div>
|
|
575341
576210
|
<!-- WO-TASK-02: Agent-emitted checklist (todos) — visible to user above conversation -->
|
|
575342
|
-
<div id="todo-checklist" style="display:none;padding:8px 16px;background
|
|
575343
|
-
<div style="font-size:0.6rem;color
|
|
576211
|
+
<div id="todo-checklist" style="display:none;padding:8px 16px;background:var(--color-bg);border-bottom:1px solid var(--color-bg-input)">
|
|
576212
|
+
<div style="font-size:0.6rem;color:var(--color-brand);text-transform:uppercase;letter-spacing:0.08em;margin-bottom:4px">Plan</div>
|
|
575344
576213
|
<ul id="todo-list" style="list-style:none;margin:0;padding:0;font-size:0.78rem"></ul>
|
|
575345
576214
|
</div>
|
|
575346
576215
|
<div id="conversation" style="flex:1;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:4px;min-height:0"></div>
|
|
@@ -575354,60 +576223,60 @@ body {
|
|
|
575354
576223
|
<option value="">+ new run</option>
|
|
575355
576224
|
</select>
|
|
575356
576225
|
<button onclick="newAgentRun()" title="Start a fresh agent run">new</button>
|
|
575357
|
-
<button onclick="deleteAgentRun()" title="Delete the current agent run" style="color
|
|
576226
|
+
<button onclick="deleteAgentRun()" title="Delete the current agent run" style="color:var(--color-error);border-color:var(--color-error)">del</button>
|
|
575358
576227
|
<button onclick="refreshAgentRunSelect()" title="Refresh the run list">⟳</button>
|
|
575359
576228
|
</div>
|
|
575360
576229
|
<!-- Body row: agent workspace sidebar (isolated from chat) | scrollable form -->
|
|
575361
576230
|
<div style="display:flex;flex:1;overflow:hidden;min-height:0">
|
|
575362
|
-
<div id="agent-workspace-sidebar" style="display:none;width:250px;background
|
|
576231
|
+
<div id="agent-workspace-sidebar" style="display:none;width:250px;background:var(--color-bg-elevated);border-right:1px solid var(--color-bg-input);overflow-y:auto;padding:8px;flex-shrink:0;font-size:0.7rem">
|
|
575363
576232
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
575364
|
-
<span style="color
|
|
575365
|
-
<button onclick="toggleAgentWorkspace()" style="background:none;border:none;color
|
|
576233
|
+
<span style="color:var(--color-brand);font-size:0.7rem;font-weight:bold">Agent Workspace</span>
|
|
576234
|
+
<button onclick="toggleAgentWorkspace()" style="background:none;border:none;color:var(--color-fg-faint);cursor:pointer;font-size:0.8rem">x</button>
|
|
575366
576235
|
</div>
|
|
575367
|
-
<div id="agent-workspace-cwd" style="color
|
|
575368
|
-
<div id="agent-workspace-tree" style="color
|
|
576236
|
+
<div id="agent-workspace-cwd" style="color:var(--color-fg-faint);font-size:0.6rem;margin-bottom:8px;word-break:break-all"></div>
|
|
576237
|
+
<div id="agent-workspace-tree" style="color:var(--color-fg)"></div>
|
|
575369
576238
|
</div>
|
|
575370
576239
|
<div style="flex:1;overflow-y:auto;padding:12px 16px;min-height:0;min-width:0">
|
|
575371
576240
|
<!-- Parameter form: all the dials for POST /v1/run -->
|
|
575372
|
-
<div id="agent-params" style="background
|
|
575373
|
-
<div style="color
|
|
576241
|
+
<div id="agent-params" style="background:var(--color-bg);border:1px solid var(--color-bg-input);border-radius:4px;padding:10px 12px;margin-bottom:10px;font-size:0.68rem">
|
|
576242
|
+
<div style="color:var(--color-brand);font-size:0.65rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:8px">Task Parameters</div>
|
|
575374
576243
|
<div style="display:grid;grid-template-columns:120px 1fr;gap:6px 12px;align-items:center">
|
|
575375
|
-
<label style="color
|
|
576244
|
+
<label style="color:var(--color-fg-muted)">Working dir</label>
|
|
575376
576245
|
<div style="display:flex;gap:4px;align-items:center">
|
|
575377
|
-
<input id="agent-working-dir" placeholder="(daemon cwd)" style="flex:1;background
|
|
575378
|
-
<button onclick="syncFromWorkspace()" title="Use workspace selection" style="background
|
|
575379
|
-
<button onclick="document.getElementById('agent-working-dir').value=''" title="Clear" style="background
|
|
576246
|
+
<input id="agent-working-dir" placeholder="(daemon cwd)" style="flex:1;background:var(--color-bg-input);border:1px solid var(--color-border);border-radius:3px;padding:4px 8px;color:var(--color-fg);font-family:inherit;font-size:0.7rem;outline:none">
|
|
576247
|
+
<button onclick="syncFromWorkspace()" title="Use workspace selection" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.65rem;cursor:pointer">from ws</button>
|
|
576248
|
+
<button onclick="document.getElementById('agent-working-dir').value=''" title="Clear" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg-subtle);padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.65rem;cursor:pointer">×</button>
|
|
575380
576249
|
</div>
|
|
575381
576250
|
|
|
575382
|
-
<label style="color
|
|
575383
|
-
<input id="agent-model-override" placeholder="(use global model)" style="background
|
|
576251
|
+
<label style="color:var(--color-fg-muted)">Model override</label>
|
|
576252
|
+
<input id="agent-model-override" placeholder="(use global model)" style="background:var(--color-bg-input);border:1px solid var(--color-border);border-radius:3px;padding:4px 8px;color:var(--color-fg);font-family:inherit;font-size:0.7rem;outline:none">
|
|
575384
576253
|
|
|
575385
|
-
<label style="color
|
|
575386
|
-
<input id="agent-max-turns" type="number" min="1" max="200" placeholder="25" style="background
|
|
576254
|
+
<label style="color:var(--color-fg-muted)">Max turns</label>
|
|
576255
|
+
<input id="agent-max-turns" type="number" min="1" max="200" placeholder="25" style="background:var(--color-bg-input);border:1px solid var(--color-border);border-radius:3px;padding:4px 8px;color:var(--color-fg);font-family:inherit;font-size:0.7rem;outline:none;width:120px">
|
|
575387
576256
|
|
|
575388
|
-
<label style="color
|
|
575389
|
-
<input id="agent-timeout-s" type="number" min="10" max="3600" placeholder="300" style="background
|
|
576257
|
+
<label style="color:var(--color-fg-muted)">Timeout (s)</label>
|
|
576258
|
+
<input id="agent-timeout-s" type="number" min="10" max="3600" placeholder="300" style="background:var(--color-bg-input);border:1px solid var(--color-border);border-radius:3px;padding:4px 8px;color:var(--color-fg);font-family:inherit;font-size:0.7rem;outline:none;width:120px">
|
|
575390
576259
|
|
|
575391
|
-
<label style="color
|
|
575392
|
-
<select id="agent-sandbox" style="background
|
|
576260
|
+
<label style="color:var(--color-fg-muted)">Sandbox</label>
|
|
576261
|
+
<select id="agent-sandbox" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.7rem">
|
|
575393
576262
|
<option value="none">none (host process)</option>
|
|
575394
576263
|
<option value="workspace">workspace (ephemeral dir)</option>
|
|
575395
576264
|
<option value="container">container (docker)</option>
|
|
575396
576265
|
</select>
|
|
575397
576266
|
|
|
575398
|
-
<label style="color
|
|
575399
|
-
<div><input type="checkbox" id="agent-stream" checked style="accent-color
|
|
576267
|
+
<label style="color:var(--color-fg-muted)">Stream events</label>
|
|
576268
|
+
<div><input type="checkbox" id="agent-stream" checked style="accent-color:var(--color-brand)"> <span style="color:var(--color-fg-faint);font-size:0.6rem">SSE with live tool calls</span></div>
|
|
575400
576269
|
|
|
575401
|
-
<label style="color
|
|
575402
|
-
<div><input type="checkbox" id="agent-isolate" style="accent-color
|
|
576270
|
+
<label style="color:var(--color-fg-muted)">Isolate workspace</label>
|
|
576271
|
+
<div><input type="checkbox" id="agent-isolate" style="accent-color:var(--color-brand)"> <span style="color:var(--color-fg-faint);font-size:0.6rem">fresh temp dir per run</span></div>
|
|
575403
576272
|
</div>
|
|
575404
576273
|
</div>
|
|
575405
576274
|
|
|
575406
|
-
<textarea id="agent-task" placeholder="Describe the task for the agent..." style="width:100%;min-height:80px;background
|
|
576275
|
+
<textarea id="agent-task" placeholder="Describe the task for the agent..." style="width:100%;min-height:80px;background:var(--color-bg-input);border:1px solid var(--color-border);border-radius:3px;padding:8px 12px;color:var(--color-fg);font-family:inherit;font-size:0.82rem;resize:vertical;outline:none;margin-bottom:8px"></textarea>
|
|
575407
576276
|
<div style="display:flex;gap:8px;margin-bottom:12px;align-items:center">
|
|
575408
|
-
<select id="agent-profile" style="background
|
|
575409
|
-
<button onclick="submitAgentTask()" id="agent-submit" style="background
|
|
575410
|
-
<button onclick="abortAgentTask()" id="agent-abort" style="display:none;background
|
|
576277
|
+
<select id="agent-profile" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.7rem"><option value="">no profile</option></select>
|
|
576278
|
+
<button onclick="submitAgentTask()" id="agent-submit" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:6px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer">run task</button>
|
|
576279
|
+
<button onclick="abortAgentTask()" id="agent-abort" style="display:none;background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:6px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer">abort</button>
|
|
575411
576280
|
</div>
|
|
575412
576281
|
<div id="agent-events" style="font-size:0.78rem;line-height:1.5"></div>
|
|
575413
576282
|
</div><!-- /scrollable agent body -->
|
|
@@ -575419,47 +576288,47 @@ body {
|
|
|
575419
576288
|
<div id="dashboard-scheduled" style="margin-bottom:16px"></div>
|
|
575420
576289
|
<div id="dashboard-services" style="margin-bottom:16px"></div>
|
|
575421
576290
|
<div id="dashboard-usage" style="margin-bottom:16px"></div>
|
|
575422
|
-
<h3 style="color
|
|
576291
|
+
<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:8px">Job History</h3>
|
|
575423
576292
|
<div id="jobs-list" style="font-size:0.78rem"></div>
|
|
575424
576293
|
</div>
|
|
575425
576294
|
<div id="config-panel" style="display:none;flex:1;overflow-y:auto;padding:12px 16px">
|
|
575426
|
-
<h3 style="color
|
|
576295
|
+
<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:12px">Server Configuration</h3>
|
|
575427
576296
|
<div id="config-content" style="font-size:0.78rem"></div>
|
|
575428
|
-
<h3 style="color
|
|
576297
|
+
<h3 style="color:var(--color-brand);font-size:0.7rem;margin:16px 0 8px">Model</h3>
|
|
575429
576298
|
<div style="display:flex;gap:8px;align-items:center">
|
|
575430
|
-
<select id="config-model-select" style="background
|
|
575431
|
-
<button onclick="switchModel()" style="background
|
|
576299
|
+
<select id="config-model-select" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.7rem;flex:1"></select>
|
|
576300
|
+
<button onclick="switchModel()" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:4px 12px;border-radius:3px;font-family:inherit;font-size:0.7rem;cursor:pointer">switch</button>
|
|
575432
576301
|
</div>
|
|
575433
|
-
<h3 style="color
|
|
575434
|
-
<div id="config-endpoint" style="font-size:0.78rem;color
|
|
575435
|
-
<
|
|
575436
|
-
<input id="config-ep-url" placeholder="https://api.example.com/v1" style="flex:1;min-width:200px;background
|
|
575437
|
-
<input id="config-ep-key" placeholder="Bearer key (optional)" type="password" style="flex:1;min-width:150px;background
|
|
575438
|
-
<select id="config-ep-type" style="background
|
|
576302
|
+
<h3 style="color:var(--color-brand);font-size:0.7rem;margin:16px 0 8px">Inference Provider</h3>
|
|
576303
|
+
<div id="config-endpoint" style="font-size:0.78rem;color:var(--color-fg-muted);margin-bottom:8px"></div>
|
|
576304
|
+
<form onsubmit="event.preventDefault(); switchEndpoint();" autocomplete="off" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:4px">
|
|
576305
|
+
<input id="config-ep-url" placeholder="https://api.example.com/v1" autocomplete="off" style="flex:1;min-width:200px;background:var(--color-bg-input);border:1px solid var(--color-border);border-radius:3px;padding:4px 8px;color:var(--color-fg);font-family:inherit;font-size:0.7rem;outline:none">
|
|
576306
|
+
<input id="config-ep-key" placeholder="Bearer key (optional)" type="password" autocomplete="new-password" style="flex:1;min-width:150px;background:var(--color-bg-input);border:1px solid var(--color-border);border-radius:3px;padding:4px 8px;color:var(--color-fg);font-family:inherit;font-size:0.7rem;outline:none">
|
|
576307
|
+
<select id="config-ep-type" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.7rem">
|
|
575439
576308
|
<option value="ollama">ollama</option><option value="vllm">vllm</option><option value="openai">openai</option>
|
|
575440
576309
|
</select>
|
|
575441
|
-
<button
|
|
575442
|
-
</
|
|
575443
|
-
<h3 style="color
|
|
576310
|
+
<button type="submit" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:4px 12px;border-radius:3px;font-family:inherit;font-size:0.7rem;cursor:pointer">set</button>
|
|
576311
|
+
</form>
|
|
576312
|
+
<h3 style="color:var(--color-brand);font-size:0.7rem;margin:16px 0 8px">Profiles</h3>
|
|
575444
576313
|
<div id="config-profiles" style="font-size:0.78rem"></div>
|
|
575445
|
-
<h3 style="color
|
|
576314
|
+
<h3 style="color:var(--color-brand);font-size:0.7rem;margin:16px 0 8px">Export Conversation</h3>
|
|
575446
576315
|
<div style="display:flex;gap:8px">
|
|
575447
|
-
<button onclick="exportChat('md')" style="background
|
|
575448
|
-
<button onclick="exportChat('json')" style="background
|
|
576316
|
+
<button onclick="exportChat('md')" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:4px 12px;border-radius:3px;font-family:inherit;font-size:0.7rem;cursor:pointer">markdown</button>
|
|
576317
|
+
<button onclick="exportChat('json')" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:4px 12px;border-radius:3px;font-family:inherit;font-size:0.7rem;cursor:pointer">JSON</button>
|
|
575449
576318
|
</div>
|
|
575450
576319
|
</div>
|
|
575451
576320
|
<div id="activity-panel" style="display:none;flex:1;overflow-y:auto;padding:12px 16px">
|
|
575452
|
-
<h3 style="color
|
|
576321
|
+
<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:12px">Recent Activity (Audit Log)</h3>
|
|
575453
576322
|
<div id="activity-feed" style="font-size:0.72rem"></div>
|
|
575454
576323
|
</div>
|
|
575455
576324
|
<div id="projects-panel" style="display:none;flex:1;overflow-y:auto;padding:12px 16px">
|
|
575456
576325
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
|
575457
|
-
<h3 style="color
|
|
575458
|
-
<div style="font-size:0.65rem;color
|
|
576326
|
+
<h3 style="color:var(--color-brand);font-size:0.7rem;margin:0">Projects</h3>
|
|
576327
|
+
<div style="font-size:0.65rem;color:var(--color-fg-subtle)">Every folder where you have run <code style="color:var(--color-brand)">oa</code> is registered here. Click to switch workspace.</div>
|
|
575459
576328
|
</div>
|
|
575460
|
-
<div id="projects-current" style="background
|
|
575461
|
-
<span style="color
|
|
575462
|
-
<span id="projects-current-root" style="color
|
|
576329
|
+
<div id="projects-current" style="background:var(--color-bg);border-left:2px solid var(--color-brand);padding:8px 12px;margin-bottom:12px;font-size:0.72rem;color:var(--color-fg)">
|
|
576330
|
+
<span style="color:var(--color-fg-subtle)">current:</span> <span id="projects-current-name" style="color:var(--color-brand)">(none)</span>
|
|
576331
|
+
<span id="projects-current-root" style="color:var(--color-fg-subtle);margin-left:8px"></span>
|
|
575463
576332
|
</div>
|
|
575464
576333
|
<div id="projects-list" style="font-size:0.72rem"></div>
|
|
575465
576334
|
</div>
|
|
@@ -575467,35 +576336,35 @@ body {
|
|
|
575467
576336
|
<!-- Voice tab — voicechat toggle + clone management. AudioWorklet drives mic capture; WebAudio handles TTS playback. -->
|
|
575468
576337
|
<div id="voice-panel" style="display:none;flex:1;overflow-y:auto;padding:12px 16px">
|
|
575469
576338
|
<div style="margin-bottom:18px">
|
|
575470
|
-
<div style="color
|
|
575471
|
-
<div style="font-size:0.65rem;color
|
|
576339
|
+
<div style="color:var(--color-brand);font-size:0.85rem;margin-bottom:4px">voicechat</div>
|
|
576340
|
+
<div style="font-size:0.65rem;color:var(--color-fg-subtle);margin-bottom:8px">
|
|
575472
576341
|
Live mic ↔ ASR ↔ agent ↔ TTS over WebSocket. Audio routes over the same origin
|
|
575473
576342
|
as this page — works on localhost or over a forwarded public IP. Mic permission
|
|
575474
576343
|
is requested when you start a session.
|
|
575475
576344
|
</div>
|
|
575476
576345
|
<div style="display:flex;gap:10px;align-items:center;margin-bottom:8px">
|
|
575477
|
-
<button id="voice-toggle-btn" onclick="toggleVoiceChat()" style="background
|
|
575478
|
-
<span id="voice-state-pill" style="background
|
|
575479
|
-
<span id="voice-mic-pill" style="display:none;background
|
|
576346
|
+
<button id="voice-toggle-btn" onclick="toggleVoiceChat()" style="background:var(--color-bg-input);border:1px solid var(--color-brand);color:var(--color-brand);padding:6px 16px;border-radius:3px;font-family:inherit;font-size:0.72rem;cursor:pointer">start voicechat</button>
|
|
576347
|
+
<span id="voice-state-pill" style="background:var(--color-bg);border-left:2px solid var(--color-fg-faint);padding:4px 10px;font-size:0.65rem;color:var(--color-fg-muted)">idle</span>
|
|
576348
|
+
<span id="voice-mic-pill" style="display:none;background:var(--color-bg);border-left:2px solid var(--color-success);padding:4px 10px;font-size:0.6rem;color:var(--color-success)">● mic active</span>
|
|
575480
576349
|
</div>
|
|
575481
|
-
<div id="voice-transcript-pane" style="display:none;background
|
|
576350
|
+
<div id="voice-transcript-pane" style="display:none;background:var(--color-bg);border:1px solid var(--color-bg-input);border-radius:3px;padding:10px;margin-top:6px;min-height:80px;max-height:280px;overflow-y:auto;font-size:0.7rem"></div>
|
|
575482
576351
|
</div>
|
|
575483
576352
|
|
|
575484
576353
|
<div style="margin-bottom:18px">
|
|
575485
|
-
<div style="color
|
|
576354
|
+
<div style="color:var(--color-brand);font-size:0.85rem;margin-bottom:4px">voice model</div>
|
|
575486
576355
|
<div style="display:flex;gap:8px;align-items:center">
|
|
575487
|
-
<select id="voice-model-select" style="background
|
|
575488
|
-
<button onclick="switchVoiceModel()" style="background
|
|
576356
|
+
<select id="voice-model-select" style="background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-fg);padding:5px 10px;border-radius:3px;font-family:inherit;font-size:0.7rem;flex:1"></select>
|
|
576357
|
+
<button onclick="switchVoiceModel()" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:5px 12px;border-radius:3px;font-family:inherit;font-size:0.7rem;cursor:pointer">switch</button>
|
|
575489
576358
|
</div>
|
|
575490
576359
|
</div>
|
|
575491
576360
|
|
|
575492
576361
|
<div style="margin-bottom:18px">
|
|
575493
576362
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
|
575494
|
-
<div style="color
|
|
575495
|
-
<button onclick="document.getElementById('clone-upload-input').click()" style="background
|
|
576363
|
+
<div style="color:var(--color-brand);font-size:0.85rem">voice clone references</div>
|
|
576364
|
+
<button onclick="document.getElementById('clone-upload-input').click()" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:4px 10px;border-radius:3px;font-family:inherit;font-size:0.65rem;cursor:pointer">+ upload</button>
|
|
575496
576365
|
<input type="file" id="clone-upload-input" accept="audio/wav,audio/mp3,audio/mpeg,audio/ogg,audio/flac,audio/m4a,audio/aac,audio/opus,.wav,.mp3,.ogg,.flac,.m4a,.aac,.opus" style="display:none" onchange="uploadCloneRef(this.files)">
|
|
575497
576366
|
</div>
|
|
575498
|
-
<div style="font-size:0.6rem;color
|
|
576367
|
+
<div style="font-size:0.6rem;color:var(--color-fg-subtle);margin-bottom:8px">
|
|
575499
576368
|
LuxTTS clones a voice from a 3+ second reference clip. Upload a clean WAV/MP3 sample,
|
|
575500
576369
|
then activate it. Active reference is used whenever the LuxTTS voice model is selected.
|
|
575501
576370
|
</div>
|
|
@@ -575503,10 +576372,10 @@ body {
|
|
|
575503
576372
|
</div>
|
|
575504
576373
|
|
|
575505
576374
|
<div style="margin-bottom:18px">
|
|
575506
|
-
<div style="color
|
|
576375
|
+
<div style="color:var(--color-brand);font-size:0.85rem;margin-bottom:4px">say something (test TTS)</div>
|
|
575507
576376
|
<div style="display:flex;gap:8px">
|
|
575508
|
-
<input type="text" id="voice-test-text" placeholder="Type and the voice model speaks it..." style="flex:1;background
|
|
575509
|
-
<button onclick="testTTS()" style="background
|
|
576377
|
+
<input type="text" id="voice-test-text" placeholder="Type and the voice model speaks it..." style="flex:1;background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-fg);padding:6px 10px;border-radius:3px;font-family:inherit;font-size:0.7rem">
|
|
576378
|
+
<button onclick="testTTS()" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:5px 14px;border-radius:3px;font-family:inherit;font-size:0.7rem;cursor:pointer">speak</button>
|
|
575510
576379
|
</div>
|
|
575511
576380
|
</div>
|
|
575512
576381
|
</div>
|
|
@@ -575518,11 +576387,11 @@ body {
|
|
|
575518
576387
|
<!-- WO-CHAT-AUTOSCROLL — tiny tag that appears far-right just above
|
|
575519
576388
|
the tasks-row when the user has scrolled up from live. Click
|
|
575520
576389
|
jumps to bottom and re-enables auto-scroll. -->
|
|
575521
|
-
<div id="scroll-bottom-tag" style="display:none;position:absolute;right:14px;top:-22px;background
|
|
576390
|
+
<div id="scroll-bottom-tag" style="display:none;position:absolute;right:14px;top:-22px;background:var(--color-bg);border:1px solid var(--color-brand);color:var(--color-brand);padding:2px 8px;border-radius:3px;font-size:0.6rem;cursor:pointer;z-index:5;line-height:1.4" onclick="scrollChatToBottom()">scroll to bottom ↓</div>
|
|
575522
576391
|
|
|
575523
576392
|
<!-- WO-TASK-06: Compact aggregated pill label above the dots row.
|
|
575524
576393
|
Hidden when no processes are active; click to toggle dots row visibility. -->
|
|
575525
|
-
<div id="processes-pill" style="display:none;padding:4px 16px;background
|
|
576394
|
+
<div id="processes-pill" style="display:none;padding:4px 16px;background:var(--color-bg);border-bottom:1px solid var(--color-bg-input);color:var(--color-fg-subtle);font-size:0.62rem;cursor:pointer" onclick="toggleProcessesRow()">
|
|
575526
576395
|
<span id="processes-pill-text">idle</span>
|
|
575527
576396
|
</div>
|
|
575528
576397
|
|
|
@@ -575538,29 +576407,174 @@ body {
|
|
|
575538
576407
|
<span class="proc-empty" id="proc-empty">idle</span>
|
|
575539
576408
|
</div>
|
|
575540
576409
|
|
|
576410
|
+
<!-- ════════════════════════════════════════════════════════════
|
|
576411
|
+
OWUI-4: Input toolbar (above the textarea)
|
|
576412
|
+
Buttons: file attach, mic toggle (uses /v1/voicechat/ws),
|
|
576413
|
+
slash palette opener, model picker (compact), system-prompt
|
|
576414
|
+
toggle. The legacy 'sys' toggle moves here too so #input-row
|
|
576415
|
+
stays focused on text + send/stop only.
|
|
576416
|
+
════════════════════════════════════════════════════════════ -->
|
|
576417
|
+
<div id="input-toolbar">
|
|
576418
|
+
<button class="ibtn" id="attach-btn" title="Attach files" onclick="document.getElementById('attach-input').click()">
|
|
576419
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
|
576420
|
+
</button>
|
|
576421
|
+
<input type="file" id="attach-input" multiple style="display:none" onchange="handleAttachInput(this.files); this.value=''">
|
|
576422
|
+
|
|
576423
|
+
<button class="ibtn" id="mic-btn" title="Toggle voicechat (Cmd/Ctrl+Shift+M)" onclick="toggleVoiceMicFromInput()">
|
|
576424
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
|
|
576425
|
+
</button>
|
|
576426
|
+
|
|
576427
|
+
<button class="ibtn" id="slash-btn" title="Slash commands (/)" onclick="openSlashPalette()">
|
|
576428
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="5" x2="5" y2="19"/></svg>
|
|
576429
|
+
</button>
|
|
576430
|
+
|
|
576431
|
+
<button class="ibtn" id="sys-btn" title="Toggle system prompt" onclick="toggleSystemPrompt()">
|
|
576432
|
+
sys
|
|
576433
|
+
</button>
|
|
576434
|
+
|
|
576435
|
+
<span class="ibtn-spacer" style="flex:1"></span>
|
|
576436
|
+
|
|
576437
|
+
<select id="input-model-mini" class="ibtn-model" onchange="if(this.value){const m=document.getElementById('model-select');if(m){m.value=this.value;m.dispatchEvent(new Event('change'));}}" title="Model"></select>
|
|
576438
|
+
</div>
|
|
576439
|
+
|
|
576440
|
+
<!-- Attachments chip row (populated by handleAttachInput) -->
|
|
576441
|
+
<div id="attach-chips" style="display:none"></div>
|
|
576442
|
+
|
|
576443
|
+
<!-- Slash command palette (floating overlay anchored above the input) -->
|
|
576444
|
+
<div id="slash-palette" style="display:none">
|
|
576445
|
+
<div id="slash-palette-list"></div>
|
|
576446
|
+
</div>
|
|
576447
|
+
|
|
575541
576448
|
<!-- Lower sub-row: existing input + send/stop -->
|
|
575542
576449
|
<div id="input-row">
|
|
575543
|
-
<span id="system-prompt-toggle" onclick="toggleSystemPrompt()">sys</span>
|
|
576450
|
+
<span id="system-prompt-toggle" onclick="toggleSystemPrompt()" style="display:none">sys</span>
|
|
575544
576451
|
<textarea id="input-area" placeholder="Type a message..." rows="1"></textarea>
|
|
575545
576452
|
<button id="send-btn" onclick="sendMessage()">send</button>
|
|
575546
|
-
<button id="stop-btn" onclick="stopChat()" style="display:none;background
|
|
576453
|
+
<button id="stop-btn" onclick="stopChat()" style="display:none;background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:10px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer;flex-shrink:0">stop</button>
|
|
575547
576454
|
<!-- WO-CHAT-CHECKIN — teal accent button that takes the place of stop
|
|
575548
576455
|
when the user starts typing during an active run. Click sends a
|
|
575549
576456
|
side-channel check-in to the triage sub-agent (route /v1/chat/check-in)
|
|
575550
576457
|
which expands the input into a steering instruction the main agent
|
|
575551
576458
|
picks up at its next turn. -->
|
|
575552
|
-
<button id="checkin-btn" onclick="sendCheckin()" style="display:none;background
|
|
576459
|
+
<button id="checkin-btn" onclick="sendCheckin()" style="display:none;background:var(--color-info);border:1px solid var(--color-info);color:var(--color-info);padding:10px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer;flex-shrink:0">check in</button>
|
|
575553
576460
|
</div>
|
|
575554
576461
|
</div>
|
|
575555
576462
|
|
|
575556
576463
|
<div id="key-modal">
|
|
575557
|
-
<
|
|
576464
|
+
<form class="modal" onsubmit="event.preventDefault(); saveKey();" autocomplete="off">
|
|
575558
576465
|
<h3>API Key</h3>
|
|
575559
|
-
<input id="key-input" type="password" placeholder="Bearer token (leave empty if auth disabled)">
|
|
576466
|
+
<input id="key-input" type="password" placeholder="Bearer token (leave empty if auth disabled)" autocomplete="new-password">
|
|
575560
576467
|
<div>
|
|
575561
|
-
<button
|
|
575562
|
-
<button onclick="clearKey()">clear</button>
|
|
575563
|
-
<button onclick="closeKeyModal()">cancel</button>
|
|
576468
|
+
<button type="submit">save</button>
|
|
576469
|
+
<button type="button" onclick="clearKey()">clear</button>
|
|
576470
|
+
<button type="button" onclick="closeKeyModal()">cancel</button>
|
|
576471
|
+
</div>
|
|
576472
|
+
</form>
|
|
576473
|
+
</div>
|
|
576474
|
+
|
|
576475
|
+
<!-- ════════════════════════════════════════════════════════════════
|
|
576476
|
+
OWUI-5: Settings modal — full-screen overlay with left-rail
|
|
576477
|
+
tabs + content pane + Save/Cancel footer.
|
|
576478
|
+
════════════════════════════════════════════════════════════════ -->
|
|
576479
|
+
<div id="settings-modal" class="oa-modal" role="dialog" aria-modal="true" aria-labelledby="settings-modal-title" style="display:none">
|
|
576480
|
+
<div class="oa-modal-backdrop" onclick="closeSettingsModal()"></div>
|
|
576481
|
+
<div class="oa-modal-window" role="document">
|
|
576482
|
+
<div class="oa-modal-header">
|
|
576483
|
+
<h3 id="settings-modal-title">Settings</h3>
|
|
576484
|
+
<button class="oa-modal-close" onclick="closeSettingsModal()" aria-label="Close">×</button>
|
|
576485
|
+
</div>
|
|
576486
|
+
<div class="oa-modal-body">
|
|
576487
|
+
<nav class="oa-modal-rail" role="tablist">
|
|
576488
|
+
<button class="rail-tab active" role="tab" data-pane="general" onclick="openSettingsPane('general')">General</button>
|
|
576489
|
+
<button class="rail-tab" role="tab" data-pane="connections" onclick="openSettingsPane('connections')">Connections</button>
|
|
576490
|
+
<button class="rail-tab" role="tab" data-pane="models" onclick="openSettingsPane('models')">Models</button>
|
|
576491
|
+
<button class="rail-tab" role="tab" data-pane="voice" onclick="openSettingsPane('voice')">Voice</button>
|
|
576492
|
+
<button class="rail-tab" role="tab" data-pane="profiles" onclick="openSettingsPane('profiles')">Profiles</button>
|
|
576493
|
+
<button class="rail-tab" role="tab" data-pane="advanced" onclick="openSettingsPane('advanced')">Advanced</button>
|
|
576494
|
+
</nav>
|
|
576495
|
+
<div class="oa-modal-pane">
|
|
576496
|
+
<section class="settings-pane active" id="settings-pane-general" role="tabpanel">
|
|
576497
|
+
<h4>Theme</h4>
|
|
576498
|
+
<label style="display:block;margin:8px 0;font-size:0.78rem">
|
|
576499
|
+
<input type="radio" name="oa-theme" value="dark" checked onchange="setOATheme(this.value)"> Dark
|
|
576500
|
+
</label>
|
|
576501
|
+
<label style="display:block;margin:8px 0;font-size:0.78rem">
|
|
576502
|
+
<input type="radio" name="oa-theme" value="light" onchange="setOATheme(this.value)"> Light
|
|
576503
|
+
</label>
|
|
576504
|
+
<h4 style="margin-top:18px">Font scale</h4>
|
|
576505
|
+
<label style="display:block;margin:8px 0;font-size:0.78rem">
|
|
576506
|
+
<input type="range" min="0.85" max="1.25" step="0.05" value="1" oninput="setOAFontScale(this.value)" style="width:200px;vertical-align:middle">
|
|
576507
|
+
<span id="oa-font-scale-display">1.00x</span>
|
|
576508
|
+
</label>
|
|
576509
|
+
</section>
|
|
576510
|
+
<section class="settings-pane" id="settings-pane-connections" role="tabpanel" hidden>
|
|
576511
|
+
<h4>Backend</h4>
|
|
576512
|
+
<p style="font-size:0.74rem;color:var(--color-fg-muted);margin:6px 0 12px">Loaded from /v1/config — edits POST /v1/config/endpoint.</p>
|
|
576513
|
+
<div id="settings-connections-host">
|
|
576514
|
+
<button onclick="loadSettingsConnections()" style="font-size:0.74rem">load current</button>
|
|
576515
|
+
</div>
|
|
576516
|
+
</section>
|
|
576517
|
+
<section class="settings-pane" id="settings-pane-models" role="tabpanel" hidden>
|
|
576518
|
+
<h4>Available models</h4>
|
|
576519
|
+
<p style="font-size:0.74rem;color:var(--color-fg-muted);margin:6px 0 12px">Sourced from /v1/models. Click a row to set it as active.</p>
|
|
576520
|
+
<input type="search" id="settings-models-search" placeholder="Filter..." oninput="filterSettingsModels(this.value)" style="width:100%;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:6px 10px;border-radius:var(--radius-sm);font-size:0.78rem;margin-bottom:8px">
|
|
576521
|
+
<div id="settings-models-list" style="max-height:300px;overflow-y:auto;font-size:0.78rem"></div>
|
|
576522
|
+
<div style="margin-top:10px"><button onclick="loadSettingsModels()" style="font-size:0.74rem">refresh</button></div>
|
|
576523
|
+
</section>
|
|
576524
|
+
<section class="settings-pane" id="settings-pane-voice" role="tabpanel" hidden>
|
|
576525
|
+
<h4>Voice clone references</h4>
|
|
576526
|
+
<p style="font-size:0.74rem;color:var(--color-fg-muted);margin:6px 0 12px">Manage TTS clone refs (relocated from the legacy Voice tab).</p>
|
|
576527
|
+
<div id="settings-voice-host">
|
|
576528
|
+
<button onclick="loadSettingsVoice()" style="font-size:0.74rem">load</button>
|
|
576529
|
+
</div>
|
|
576530
|
+
</section>
|
|
576531
|
+
<section class="settings-pane" id="settings-pane-profiles" role="tabpanel" hidden>
|
|
576532
|
+
<h4>Agent profiles</h4>
|
|
576533
|
+
<p style="font-size:0.74rem;color:var(--color-fg-muted);margin:6px 0 12px">Same as Agent tab profile list.</p>
|
|
576534
|
+
<div id="settings-profiles-host">
|
|
576535
|
+
<button onclick="loadSettingsProfiles()" style="font-size:0.74rem">load</button>
|
|
576536
|
+
</div>
|
|
576537
|
+
</section>
|
|
576538
|
+
<section class="settings-pane" id="settings-pane-advanced" role="tabpanel" hidden>
|
|
576539
|
+
<h4>Advanced</h4>
|
|
576540
|
+
<p style="font-size:0.74rem;color:var(--color-fg-muted);margin:6px 0 12px">Diagnostics, raw config, danger zone.</p>
|
|
576541
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
576542
|
+
<button onclick="window.open('/api/docs', '_blank')" style="font-size:0.74rem">open swagger</button>
|
|
576543
|
+
<button onclick="window.open('/redoc', '_blank')" style="font-size:0.74rem">open redoc</button>
|
|
576544
|
+
<button onclick="window.open('/asyncapi.json', '_blank')" style="font-size:0.74rem">asyncapi spec</button>
|
|
576545
|
+
<button onclick="window.open('/openapi.json', '_blank')" style="font-size:0.74rem">openapi spec</button>
|
|
576546
|
+
</div>
|
|
576547
|
+
</section>
|
|
576548
|
+
</div>
|
|
576549
|
+
</div>
|
|
576550
|
+
<div class="oa-modal-footer">
|
|
576551
|
+
<button class="oa-btn-secondary" onclick="closeSettingsModal()">Close</button>
|
|
576552
|
+
</div>
|
|
576553
|
+
</div>
|
|
576554
|
+
</div>
|
|
576555
|
+
|
|
576556
|
+
<!-- ════════════════════════════════════════════════════════════════
|
|
576557
|
+
OWUI-5: Model manager modal — search + list + details.
|
|
576558
|
+
════════════════════════════════════════════════════════════════ -->
|
|
576559
|
+
<div id="model-manager-modal" class="oa-modal" role="dialog" aria-modal="true" aria-labelledby="model-manager-title" style="display:none">
|
|
576560
|
+
<div class="oa-modal-backdrop" onclick="closeModelManager()"></div>
|
|
576561
|
+
<div class="oa-modal-window" role="document">
|
|
576562
|
+
<div class="oa-modal-header">
|
|
576563
|
+
<h3 id="model-manager-title">Model manager</h3>
|
|
576564
|
+
<button class="oa-modal-close" onclick="closeModelManager()" aria-label="Close">×</button>
|
|
576565
|
+
</div>
|
|
576566
|
+
<div class="oa-modal-body" style="grid-template-columns:1fr 280px">
|
|
576567
|
+
<div class="oa-modal-pane" style="padding:14px">
|
|
576568
|
+
<input type="search" id="mm-search" placeholder="Search models..." oninput="filterModelManager(this.value)" style="width:100%;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:8px 12px;border-radius:var(--radius-sm);font-size:0.82rem;margin-bottom:10px">
|
|
576569
|
+
<div id="mm-list" style="max-height:420px;overflow-y:auto"></div>
|
|
576570
|
+
</div>
|
|
576571
|
+
<aside class="oa-modal-aside" id="mm-detail">
|
|
576572
|
+
<div style="padding:14px;color:var(--color-fg-muted);font-size:0.78rem">Select a model on the left.</div>
|
|
576573
|
+
</aside>
|
|
576574
|
+
</div>
|
|
576575
|
+
<div class="oa-modal-footer">
|
|
576576
|
+
<button class="oa-btn-secondary" onclick="loadModelManager()">refresh</button>
|
|
576577
|
+
<button class="oa-btn-secondary" onclick="closeModelManager()">Close</button>
|
|
575564
576578
|
</div>
|
|
575565
576579
|
</div>
|
|
575566
576580
|
</div>
|
|
@@ -575917,7 +576931,10 @@ if (conv) {
|
|
|
575917
576931
|
// Auto-resize textarea + WO-CHAT-CHECKIN typing detection
|
|
575918
576932
|
input.addEventListener('input', () => {
|
|
575919
576933
|
input.style.height = 'auto';
|
|
575920
|
-
|
|
576934
|
+
// OWUI-4: bump max from 120 (~6 lines) to 240 (~12 lines).
|
|
576935
|
+
input.style.height = Math.min(input.scrollHeight, 240) + 'px';
|
|
576936
|
+
// OWUI-4: open slash palette as soon as the input starts with "/".
|
|
576937
|
+
try { _maybeUpdateSlashPalette(); } catch {}
|
|
575921
576938
|
// While a run is streaming, swap stop button → check-in button as
|
|
575922
576939
|
// soon as the user types anything. When the input is cleared, swap back.
|
|
575923
576940
|
if (streaming) {
|
|
@@ -575933,6 +576950,22 @@ input.addEventListener('input', () => {
|
|
|
575933
576950
|
}
|
|
575934
576951
|
});
|
|
575935
576952
|
input.addEventListener('keydown', (e) => {
|
|
576953
|
+
// OWUI-4: arrow / enter / escape navigation for the slash palette
|
|
576954
|
+
// takes priority over the normal Enter behaviour.
|
|
576955
|
+
const palOpen = _slashPaletteIsOpen();
|
|
576956
|
+
if (palOpen) {
|
|
576957
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); _slashPaletteMove(1); return; }
|
|
576958
|
+
if (e.key === 'ArrowUp') { e.preventDefault(); _slashPaletteMove(-1); return; }
|
|
576959
|
+
if (e.key === 'Escape') { e.preventDefault(); closeSlashPalette(); return; }
|
|
576960
|
+
if (e.key === 'Tab') { e.preventDefault(); _slashPaletteAccept(); return; }
|
|
576961
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); _slashPaletteAccept(); return; }
|
|
576962
|
+
}
|
|
576963
|
+
// OWUI-4: Cmd/Ctrl+Enter — always sends, never inserts a newline.
|
|
576964
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
576965
|
+
e.preventDefault();
|
|
576966
|
+
if (streaming && input.value.trim().length > 0) sendCheckin(); else sendMessage();
|
|
576967
|
+
return;
|
|
576968
|
+
}
|
|
575936
576969
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
575937
576970
|
e.preventDefault();
|
|
575938
576971
|
// While a run is streaming, Enter dispatches a check-in instead of
|
|
@@ -576041,29 +577074,106 @@ function persistSelectedModel() {
|
|
|
576041
577074
|
function addMessage(role, content) {
|
|
576042
577075
|
const div = document.createElement('div');
|
|
576043
577076
|
div.className = 'msg ' + role;
|
|
576044
|
-
|
|
576045
|
-
//
|
|
576046
|
-
//
|
|
577077
|
+
div.dataset.role = role;
|
|
577078
|
+
// OWUI-3: wrap user/system content in an inner element so the bubble/
|
|
577079
|
+
// chip CSS can target it via .msg.user > * / .msg.system > * .
|
|
577080
|
+
// Assistant messages stay structurally flat — the avatar is rendered
|
|
577081
|
+
// via ::before pseudo-element so all child nodes are content.
|
|
577082
|
+
let host = div;
|
|
577083
|
+
if (role === 'user' || role === 'system') {
|
|
577084
|
+
host = document.createElement('div');
|
|
577085
|
+
host.className = 'msg-bubble';
|
|
577086
|
+
div.appendChild(host);
|
|
577087
|
+
}
|
|
576047
577088
|
if (role === 'assistant' || (role === 'user' && looksLikeMarkdown(content))) {
|
|
576048
|
-
|
|
577089
|
+
host.innerHTML = renderMarkdown(content);
|
|
576049
577090
|
} else {
|
|
576050
|
-
|
|
577091
|
+
host.textContent = content;
|
|
576051
577092
|
}
|
|
576052
|
-
//
|
|
577093
|
+
// OWUI-3: rich per-message action row on assistant messages.
|
|
576053
577094
|
if (role === 'assistant') {
|
|
576054
577095
|
const actions = document.createElement('div');
|
|
576055
577096
|
actions.className = 'msg-actions';
|
|
576056
|
-
const
|
|
576057
|
-
|
|
576058
|
-
|
|
576059
|
-
|
|
577097
|
+
const mkBtn = (label, title, fn) => {
|
|
577098
|
+
const b = document.createElement('button');
|
|
577099
|
+
b.textContent = label;
|
|
577100
|
+
b.title = title;
|
|
577101
|
+
b.onclick = fn;
|
|
577102
|
+
return b;
|
|
577103
|
+
};
|
|
577104
|
+
actions.appendChild(mkBtn('copy', 'Copy message', () => {
|
|
577105
|
+
// Copy what's actually visible (post-render text), not the raw arg —
|
|
577106
|
+
// the closure-captured "content" may be empty when this message was
|
|
577107
|
+
// built incrementally during streaming.
|
|
577108
|
+
const txt = host.innerText || host.textContent || content || '';
|
|
577109
|
+
try { navigator.clipboard.writeText(txt); } catch {}
|
|
577110
|
+
const orig = actions.querySelector('button').textContent;
|
|
577111
|
+
actions.querySelector('button').textContent = 'copied';
|
|
577112
|
+
setTimeout(() => { actions.querySelector('button').textContent = orig; }, 1500);
|
|
577113
|
+
}));
|
|
577114
|
+
actions.appendChild(mkBtn('regen', 'Regenerate this response', () => {
|
|
577115
|
+
try { regenerateAssistantMessage(div); } catch (e) { console.warn('regen failed', e); }
|
|
577116
|
+
}));
|
|
577117
|
+
actions.appendChild(mkBtn('del', 'Delete this message', () => {
|
|
577118
|
+
try { deleteMessageDiv(div); } catch (e) { console.warn('del failed', e); }
|
|
577119
|
+
}));
|
|
576060
577120
|
div.appendChild(actions);
|
|
576061
577121
|
}
|
|
576062
577122
|
conv.appendChild(div);
|
|
577123
|
+
// OWUI-3: schedule syntax highlight for any new code blocks.
|
|
577124
|
+
try { _highlightCodeBlocks(div); } catch {}
|
|
576063
577125
|
maybeAutoScroll();
|
|
576064
577126
|
return div;
|
|
576065
577127
|
}
|
|
576066
577128
|
|
|
577129
|
+
// OWUI-3: regenerate by re-running the most recent user message's send
|
|
577130
|
+
// flow. We delete the assistant message + any messages after the
|
|
577131
|
+
// preceding user message, then re-dispatch send().
|
|
577132
|
+
function regenerateAssistantMessage(msgDiv) {
|
|
577133
|
+
if (!msgDiv || !conv || streaming) return;
|
|
577134
|
+
// Walk backwards in the DOM to find the immediately-preceding user msg.
|
|
577135
|
+
let prev = msgDiv.previousElementSibling;
|
|
577136
|
+
while (prev && !(prev.classList && prev.classList.contains('msg') && prev.dataset.role === 'user')) {
|
|
577137
|
+
prev = prev.previousElementSibling;
|
|
577138
|
+
}
|
|
577139
|
+
if (!prev) return;
|
|
577140
|
+
const userText = (prev.querySelector('.msg-bubble')?.innerText || prev.innerText || '').trim();
|
|
577141
|
+
if (!userText) return;
|
|
577142
|
+
// Drop the assistant + any siblings between user and assistant.
|
|
577143
|
+
let cur = prev.nextElementSibling;
|
|
577144
|
+
while (cur) {
|
|
577145
|
+
const next = cur.nextElementSibling;
|
|
577146
|
+
cur.remove();
|
|
577147
|
+
cur = next;
|
|
577148
|
+
}
|
|
577149
|
+
// Drop the matching backend message-history entry too so it doesn't get
|
|
577150
|
+
// double-counted on resend.
|
|
577151
|
+
if (Array.isArray(messages)) {
|
|
577152
|
+
while (messages.length && messages[messages.length - 1].role !== 'user') messages.pop();
|
|
577153
|
+
if (messages.length && messages[messages.length - 1].role === 'user') messages.pop();
|
|
577154
|
+
}
|
|
577155
|
+
// Now also drop the user's DOM bubble — send() re-adds it.
|
|
577156
|
+
prev.remove();
|
|
577157
|
+
// Repopulate the input and re-fire send.
|
|
577158
|
+
input.value = userText;
|
|
577159
|
+
try { send(); } catch (e) { console.warn('regen send failed', e); }
|
|
577160
|
+
}
|
|
577161
|
+
|
|
577162
|
+
// OWUI-3: delete a single message div from the DOM + memory.
|
|
577163
|
+
// Trims trailing message-history entries beyond the deleted index so the
|
|
577164
|
+
// next turn doesn't carry orphaned context.
|
|
577165
|
+
function deleteMessageDiv(msgDiv) {
|
|
577166
|
+
if (!msgDiv) return;
|
|
577167
|
+
const role = msgDiv.dataset.role;
|
|
577168
|
+
msgDiv.remove();
|
|
577169
|
+
if (Array.isArray(messages)) {
|
|
577170
|
+
// Remove the LAST entry matching this role (best-effort).
|
|
577171
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
577172
|
+
if (messages[i].role === role) { messages.splice(i, 1); break; }
|
|
577173
|
+
}
|
|
577174
|
+
}
|
|
577175
|
+
}
|
|
577176
|
+
|
|
576067
577177
|
// WO-TASK-02 — heuristic detection: does this string contain markdown
|
|
576068
577178
|
// constructs that warrant parsing? Used for .msg.user content where we
|
|
576069
577179
|
// don't want to render every plain string as HTML, but we DO want to
|
|
@@ -576115,14 +577225,14 @@ function renderMarkdown(text) {
|
|
|
576115
577225
|
// Headers — h1..h6
|
|
576116
577226
|
text = text.replace(/(^|\\n)(#{1,6}) (.+?)(\\n|$)/g, (_, pre, hashes, body, post) => {
|
|
576117
577227
|
const lvl = hashes.length;
|
|
576118
|
-
return pre + '<h' + lvl + ' style="margin:6px 0 4px;color
|
|
577228
|
+
return pre + '<h' + lvl + ' style="margin:6px 0 4px;color:var(--color-brand);font-size:' + (1.05 - lvl * 0.05) + 'rem">' + body + '</h' + lvl + '>' + (post === '\\n' ? '' : post);
|
|
576119
577229
|
});
|
|
576120
577230
|
// Blockquote
|
|
576121
577231
|
text = text.replace(/(^|\\n)> (.+?)(\\n|$)/g, (_, pre, body, post) => {
|
|
576122
|
-
return pre + '<blockquote style="margin:4px 0;padding:2px 8px;border-left:2px solid
|
|
577232
|
+
return pre + '<blockquote style="margin:4px 0;padding:2px 8px;border-left:2px solid var(--color-brand);color:var(--color-fg-muted)">' + body + '</blockquote>' + (post === '\\n' ? '' : post);
|
|
576123
577233
|
});
|
|
576124
577234
|
// Horizontal rule
|
|
576125
|
-
text = text.replace(/(^|\\n)(?:---|\\*\\*\\*|___)(\\n|$)/g, '$1<hr style="border:none;border-top:1px solid
|
|
577235
|
+
text = text.replace(/(^|\\n)(?:---|\\*\\*\\*|___)(\\n|$)/g, '$1<hr style="border:none;border-top:1px solid var(--color-bg-input);margin:8px 0">$2');
|
|
576126
577236
|
// Unordered lists — collapse consecutive items into a single <ul>
|
|
576127
577237
|
text = text.replace(/(?:(^|\\n)[-*+] [^\\n]+)+/g, m => {
|
|
576128
577238
|
const items = m.trim().split(/\\n/).map(l => l.replace(/^[-*+] /, '')).map(l => '<li>' + l + '</li>').join('');
|
|
@@ -576146,7 +577256,7 @@ function renderMarkdown(text) {
|
|
|
576146
577256
|
text = text.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_, label, url) => {
|
|
576147
577257
|
// Only allow http(s) or relative URLs to prevent javascript: links
|
|
576148
577258
|
const safe = /^(https?:\\/\\/|\\/|\\.\\.?\\/|#)/.test(url) ? url : '#';
|
|
576149
|
-
return '<a href="' + safe + '" target="_blank" rel="noopener noreferrer" style="color
|
|
577259
|
+
return '<a href="' + safe + '" target="_blank" rel="noopener noreferrer" style="color:var(--color-brand)">' + label + '</a>';
|
|
576150
577260
|
});
|
|
576151
577261
|
|
|
576152
577262
|
// 5) Re-insert code blocks
|
|
@@ -576183,7 +577293,7 @@ function appendExpandableContent(parent, fullText, opts) {
|
|
|
576183
577293
|
if (isLong) {
|
|
576184
577294
|
const btn = document.createElement('button');
|
|
576185
577295
|
btn.type = 'button';
|
|
576186
|
-
btn.style.cssText = 'align-self:flex-start;margin-top:2px;padding:2px 8px;background
|
|
577296
|
+
btn.style.cssText = 'align-self:flex-start;margin-top:2px;padding:2px 8px;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);font-size:0.6rem;border-radius:2px;cursor:pointer;font-family:inherit;';
|
|
576187
577297
|
btn.textContent = 'Show more (' + text.length + ' chars)';
|
|
576188
577298
|
let expanded = false;
|
|
576189
577299
|
btn.addEventListener('click', (e) => {
|
|
@@ -576206,9 +577316,9 @@ function appendExpandableContent(parent, fullText, opts) {
|
|
|
576206
577316
|
// session message shape ({role:'tool_call', tool, args}).
|
|
576207
577317
|
function renderToolCallEvent(parent, chunkLike) {
|
|
576208
577318
|
const details = document.createElement('details');
|
|
576209
|
-
details.style.cssText = 'background
|
|
577319
|
+
details.style.cssText = 'background:var(--color-bg-elevated);border-left:2px solid var(--color-brand);margin:2px 0;font-size:0.7rem';
|
|
576210
577320
|
const summary = document.createElement('summary');
|
|
576211
|
-
summary.style.cssText = 'padding:4px 8px;color
|
|
577321
|
+
summary.style.cssText = 'padding:4px 8px;color:var(--color-brand);cursor:pointer';
|
|
576212
577322
|
|
|
576213
577323
|
const toolName = chunkLike.tool || 'tool';
|
|
576214
577324
|
let a = (chunkLike.args && typeof chunkLike.args === 'object') ? chunkLike.args : {};
|
|
@@ -576253,18 +577363,18 @@ function renderToolCallEvent(parent, chunkLike) {
|
|
|
576253
577363
|
|
|
576254
577364
|
if (a && typeof a === 'object') {
|
|
576255
577365
|
const argsDiv = document.createElement('div');
|
|
576256
|
-
argsDiv.style.cssText = 'padding:4px 8px 6px 16px;color
|
|
577366
|
+
argsDiv.style.cssText = 'padding:4px 8px 6px 16px;color:var(--color-fg-muted);font-size:0.65rem;border-top:1px solid var(--color-bg-input)';
|
|
576257
577367
|
if (toolName === 'todo_write' && Array.isArray(a.todos)) {
|
|
576258
577368
|
for (const t of a.todos) {
|
|
576259
577369
|
if (!t || typeof t !== 'object') continue;
|
|
576260
577370
|
const row = document.createElement('div');
|
|
576261
577371
|
row.style.cssText = 'padding:2px 0';
|
|
576262
577372
|
let mark = '\\u25CB';
|
|
576263
|
-
let color = '
|
|
577373
|
+
let color = 'var(--color-fg-subtle)';
|
|
576264
577374
|
if (t.status === 'completed') { mark = '\\u25C9'; color = '#4a7a4a'; }
|
|
576265
|
-
else if (t.status === 'in_progress') { mark = '\\u25D0'; color = '
|
|
576266
|
-
else if (t.status === 'blocked') { mark = '\\u25CD'; color = '
|
|
576267
|
-
row.innerHTML = '<span style="color:' + color + '">' + mark + '</span> <span style="color
|
|
577375
|
+
else if (t.status === 'in_progress') { mark = '\\u25D0'; color = 'var(--color-brand)'; }
|
|
577376
|
+
else if (t.status === 'blocked') { mark = '\\u25CD'; color = 'var(--color-error)'; }
|
|
577377
|
+
row.innerHTML = '<span style="color:' + color + '">' + mark + '</span> <span style="color:var(--color-fg)">' + escHtml(String(t.content || '').slice(0, 300)) + '</span>' + (t.blocker ? ' <span style="color:var(--color-error)">(blocked: ' + escHtml(String(t.blocker).slice(0, 100)) + ')</span>' : '');
|
|
576268
577378
|
argsDiv.appendChild(row);
|
|
576269
577379
|
}
|
|
576270
577380
|
} else {
|
|
@@ -576278,13 +577388,13 @@ function renderToolCallEvent(parent, chunkLike) {
|
|
|
576278
577388
|
else { try { vs = JSON.stringify(v, null, 2); } catch { vs = '[object]'; } }
|
|
576279
577389
|
|
|
576280
577390
|
const keyEl = document.createElement('span');
|
|
576281
|
-
keyEl.style.cssText = 'color
|
|
577391
|
+
keyEl.style.cssText = 'color:var(--color-brand);min-width:60px;flex-shrink:0';
|
|
576282
577392
|
keyEl.textContent = k;
|
|
576283
577393
|
row.appendChild(keyEl);
|
|
576284
577394
|
|
|
576285
577395
|
const valWrap = document.createElement('div');
|
|
576286
|
-
valWrap.style.cssText = 'flex:1;min-width:0;color
|
|
576287
|
-
appendExpandableContent(valWrap, vs, { truncateAt: 500, baseStyle: 'color
|
|
577396
|
+
valWrap.style.cssText = 'flex:1;min-width:0;color:var(--color-fg)';
|
|
577397
|
+
appendExpandableContent(valWrap, vs, { truncateAt: 500, baseStyle: 'color:var(--color-fg);' });
|
|
576288
577398
|
row.appendChild(valWrap);
|
|
576289
577399
|
|
|
576290
577400
|
argsDiv.appendChild(row);
|
|
@@ -576301,8 +577411,8 @@ function renderToolCallEvent(parent, chunkLike) {
|
|
|
576301
577411
|
function renderToolResultEvent(parent, chunkLike) {
|
|
576302
577412
|
const resultEl = document.createElement('div');
|
|
576303
577413
|
const errStyle = chunkLike.success === false
|
|
576304
|
-
? 'background:#2a1e1e;border-left:2px solid
|
|
576305
|
-
: 'background
|
|
577414
|
+
? 'background:#2a1e1e;border-left:2px solid var(--color-error);color:var(--color-error);'
|
|
577415
|
+
: 'background:var(--color-bg-elevated);color:var(--color-fg-muted);';
|
|
576306
577416
|
resultEl.style.cssText = errStyle + 'padding:4px 8px 4px 18px;margin:0 0 2px 0;font-size:0.65rem';
|
|
576307
577417
|
appendExpandableContent(resultEl, chunkLike.output || '', { truncateAt: 150, baseStyle: 'color:inherit;' });
|
|
576308
577418
|
parent.appendChild(resultEl);
|
|
@@ -576315,9 +577425,9 @@ function renderToolResultEvent(parent, chunkLike) {
|
|
|
576315
577425
|
// active assistant turn's tool dropdowns.
|
|
576316
577426
|
function renderCheckinEvent(parent, content) {
|
|
576317
577427
|
const el = document.createElement('div');
|
|
576318
|
-
el.style.cssText = 'background
|
|
577428
|
+
el.style.cssText = 'background:var(--color-info);border-left:3px solid var(--color-info);color:#7fdada;padding:6px 10px 6px 14px;margin:3px 0;font-size:0.7rem;font-family:inherit';
|
|
576319
577429
|
const label = document.createElement('div');
|
|
576320
|
-
label.style.cssText = 'color
|
|
577430
|
+
label.style.cssText = 'color:var(--color-info);font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px';
|
|
576321
577431
|
label.textContent = '\\u25B8 user check-in';
|
|
576322
577432
|
el.appendChild(label);
|
|
576323
577433
|
const body = document.createElement('div');
|
|
@@ -576334,9 +577444,9 @@ function renderCheckinEvent(parent, content) {
|
|
|
576334
577444
|
// stack of dropdowns.
|
|
576335
577445
|
function renderTriageResponseEvent(parent, ack, steering) {
|
|
576336
577446
|
const el = document.createElement('div');
|
|
576337
|
-
el.style.cssText = 'background:#0a2628;border-left:3px solid
|
|
577447
|
+
el.style.cssText = 'background:#0a2628;border-left:3px solid var(--color-info);color:#7fdada;padding:6px 10px 6px 14px;margin:3px 0;font-size:0.7rem';
|
|
576338
577448
|
const label = document.createElement('div');
|
|
576339
|
-
label.style.cssText = 'color
|
|
577449
|
+
label.style.cssText = 'color:var(--color-info);font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px';
|
|
576340
577450
|
label.textContent = '\\u25B8 triage \\u2192 main agent';
|
|
576341
577451
|
el.appendChild(label);
|
|
576342
577452
|
if (ack) {
|
|
@@ -576408,7 +577518,7 @@ async function sendCheckin() {
|
|
|
576408
577518
|
if (!r.ok) {
|
|
576409
577519
|
if (toolsContainer) {
|
|
576410
577520
|
const errEl = document.createElement('div');
|
|
576411
|
-
errEl.style.cssText = 'color
|
|
577521
|
+
errEl.style.cssText = 'color:var(--color-error);font-size:0.6rem;padding:2px 14px';
|
|
576412
577522
|
errEl.textContent = 'Check-in failed: HTTP ' + r.status;
|
|
576413
577523
|
toolsContainer.appendChild(errEl);
|
|
576414
577524
|
}
|
|
@@ -576422,7 +577532,7 @@ async function sendCheckin() {
|
|
|
576422
577532
|
} catch (err) {
|
|
576423
577533
|
if (toolsContainer) {
|
|
576424
577534
|
const errEl = document.createElement('div');
|
|
576425
|
-
errEl.style.cssText = 'color
|
|
577535
|
+
errEl.style.cssText = 'color:var(--color-error);font-size:0.6rem;padding:2px 14px';
|
|
576426
577536
|
errEl.textContent = 'Check-in network error: ' + (err && err.message || String(err));
|
|
576427
577537
|
toolsContainer.appendChild(errEl);
|
|
576428
577538
|
}
|
|
@@ -576465,6 +577575,11 @@ async function sendMessage() {
|
|
|
576465
577575
|
const contentDiv = document.createElement('div');
|
|
576466
577576
|
msgDiv.appendChild(contentDiv);
|
|
576467
577577
|
|
|
577578
|
+
// OWUI-3: streaming indicator — pulsing dot + 'thinking' label
|
|
577579
|
+
// Stays visible until the first content chunk arrives, then is
|
|
577580
|
+
// replaced by the running content. Hidden in the finally{} block too.
|
|
577581
|
+
showStreamingIndicator(msgDiv, 'thinking');
|
|
577582
|
+
|
|
576468
577583
|
try {
|
|
576469
577584
|
// Prepend context files as a FILES block so the agent can read them
|
|
576470
577585
|
// without having to guess paths. The user picked these via right-click
|
|
@@ -576568,9 +577683,9 @@ async function sendMessage() {
|
|
|
576568
577683
|
if (chunk.type === 'tool_call') {
|
|
576569
577684
|
chatTools.push(chunk);
|
|
576570
577685
|
const details = document.createElement('details');
|
|
576571
|
-
details.style.cssText = 'background
|
|
577686
|
+
details.style.cssText = 'background:var(--color-bg-elevated);border-left:2px solid var(--color-brand);margin:2px 0;font-size:0.7rem';
|
|
576572
577687
|
const summary = document.createElement('summary');
|
|
576573
|
-
summary.style.cssText = 'padding:4px 8px;color
|
|
577688
|
+
summary.style.cssText = 'padding:4px 8px;color:var(--color-brand);cursor:pointer';
|
|
576574
577689
|
|
|
576575
577690
|
// Build a compact one-line label so the user sees what the tool
|
|
576576
577691
|
// is actually doing without expanding. todo_write gets a special
|
|
@@ -576628,18 +577743,18 @@ async function sendMessage() {
|
|
|
576628
577743
|
// todo_write specifically render a checklist instead of a blob.
|
|
576629
577744
|
if (chunk.args && typeof chunk.args === 'object') {
|
|
576630
577745
|
const argsDiv = document.createElement('div');
|
|
576631
|
-
argsDiv.style.cssText = 'padding:4px 8px 6px 16px;color
|
|
577746
|
+
argsDiv.style.cssText = 'padding:4px 8px 6px 16px;color:var(--color-fg-muted);font-size:0.65rem;border-top:1px solid var(--color-bg-input)';
|
|
576632
577747
|
if (toolName === 'todo_write' && Array.isArray(a.todos)) {
|
|
576633
577748
|
for (const t of a.todos) {
|
|
576634
577749
|
if (!t || typeof t !== 'object') continue;
|
|
576635
577750
|
const row = document.createElement('div');
|
|
576636
577751
|
row.style.cssText = 'padding:2px 0';
|
|
576637
577752
|
let mark = '○';
|
|
576638
|
-
let color = '
|
|
577753
|
+
let color = 'var(--color-fg-subtle)';
|
|
576639
577754
|
if (t.status === 'completed') { mark = '◉'; color = '#4a7a4a'; }
|
|
576640
|
-
else if (t.status === 'in_progress') { mark = '◐'; color = '
|
|
576641
|
-
else if (t.status === 'blocked') { mark = '◍'; color = '
|
|
576642
|
-
row.innerHTML = '<span style="color:' + color + '">' + mark + '</span> <span style="color
|
|
577755
|
+
else if (t.status === 'in_progress') { mark = '◐'; color = 'var(--color-brand)'; }
|
|
577756
|
+
else if (t.status === 'blocked') { mark = '◍'; color = 'var(--color-error)'; }
|
|
577757
|
+
row.innerHTML = '<span style="color:' + color + '">' + mark + '</span> <span style="color:var(--color-fg)">' + escHtml(String(t.content || '').slice(0, 300)) + '</span>' + (t.blocker ? ' <span style="color:var(--color-error)">(blocked: ' + escHtml(String(t.blocker).slice(0, 100)) + ')</span>' : '');
|
|
576643
577758
|
argsDiv.appendChild(row);
|
|
576644
577759
|
}
|
|
576645
577760
|
} else {
|
|
@@ -576654,15 +577769,15 @@ async function sendMessage() {
|
|
|
576654
577769
|
else { try { vs = JSON.stringify(v, null, 2); } catch { vs = '[object]'; } }
|
|
576655
577770
|
|
|
576656
577771
|
const keyEl = document.createElement('span');
|
|
576657
|
-
keyEl.style.cssText = 'color
|
|
577772
|
+
keyEl.style.cssText = 'color:var(--color-brand);min-width:60px;flex-shrink:0';
|
|
576658
577773
|
keyEl.textContent = k;
|
|
576659
577774
|
row.appendChild(keyEl);
|
|
576660
577775
|
|
|
576661
577776
|
const valWrap = document.createElement('div');
|
|
576662
|
-
valWrap.style.cssText = 'flex:1;min-width:0;color
|
|
577777
|
+
valWrap.style.cssText = 'flex:1;min-width:0;color:var(--color-fg)';
|
|
576663
577778
|
// Use the show-more helper so long values are collapsed
|
|
576664
577779
|
// by default and the user can expand inline.
|
|
576665
|
-
appendExpandableContent(valWrap, vs, { truncateAt: 500, baseStyle: 'color
|
|
577780
|
+
appendExpandableContent(valWrap, vs, { truncateAt: 500, baseStyle: 'color:var(--color-fg);' });
|
|
576666
577781
|
row.appendChild(valWrap);
|
|
576667
577782
|
|
|
576668
577783
|
argsDiv.appendChild(row);
|
|
@@ -576680,8 +577795,8 @@ async function sendMessage() {
|
|
|
576680
577795
|
// 150 chars. The button sits underneath the result block.
|
|
576681
577796
|
if (chunk.type === 'tool_result') {
|
|
576682
577797
|
const resultEl = document.createElement('div');
|
|
576683
|
-
resultEl.style.cssText = 'background
|
|
576684
|
-
appendExpandableContent(resultEl, chunk.output || '', { truncateAt: 150, baseStyle: 'color
|
|
577798
|
+
resultEl.style.cssText = 'background:var(--color-bg-elevated);padding:4px 8px 4px 18px;margin:0 0 2px 0;color:var(--color-fg-muted);font-size:0.65rem';
|
|
577799
|
+
appendExpandableContent(resultEl, chunk.output || '', { truncateAt: 150, baseStyle: 'color:var(--color-fg-muted);' });
|
|
576685
577800
|
toolsContainer.appendChild(resultEl);
|
|
576686
577801
|
continue;
|
|
576687
577802
|
}
|
|
@@ -576695,8 +577810,15 @@ async function sendMessage() {
|
|
|
576695
577810
|
// Content delta
|
|
576696
577811
|
const delta = chunk.choices?.[0]?.delta?.content || '';
|
|
576697
577812
|
if (delta) {
|
|
577813
|
+
// OWUI-3: switch indicator from 'thinking' -> 'writing' on
|
|
577814
|
+
// first delta, and remove on subsequent deltas (it lives at
|
|
577815
|
+
// the bottom of the message, below content).
|
|
577816
|
+
if (!fullContent) {
|
|
577817
|
+
showStreamingIndicator(msgDiv, 'writing');
|
|
577818
|
+
}
|
|
576698
577819
|
fullContent += delta;
|
|
576699
577820
|
contentDiv.innerHTML = renderMarkdown(fullContent);
|
|
577821
|
+
try { _highlightCodeBlocks(contentDiv); } catch {}
|
|
576700
577822
|
maybeAutoScroll();
|
|
576701
577823
|
}
|
|
576702
577824
|
} catch {}
|
|
@@ -576718,11 +577840,14 @@ async function sendMessage() {
|
|
|
576718
577840
|
|
|
576719
577841
|
// Final render: content + collapsible tools + metadata
|
|
576720
577842
|
contentDiv.innerHTML = renderMarkdown(fullContent);
|
|
577843
|
+
// OWUI-3: streaming complete — drop the pulsing dot.
|
|
577844
|
+
hideStreamingIndicator(msgDiv);
|
|
577845
|
+
try { _highlightCodeBlocks(contentDiv); } catch {}
|
|
576721
577846
|
|
|
576722
577847
|
// Metadata bar: turns, tokens, duration (always shown, compact)
|
|
576723
577848
|
if (metaInfo) {
|
|
576724
577849
|
const metaBar = document.createElement('div');
|
|
576725
|
-
metaBar.style.cssText = 'margin:6px 0 2px;padding:4px 8px;background
|
|
577850
|
+
metaBar.style.cssText = 'margin:6px 0 2px;padding:4px 8px;background:var(--color-bg-elevated);border-radius:3px;font-size:0.6rem;color:var(--color-fg-faint);display:flex;gap:12px;flex-wrap:wrap';
|
|
576726
577851
|
const parts = [];
|
|
576727
577852
|
if (metaInfo.turns) parts.push(metaInfo.turns + ' turn' + (metaInfo.turns > 1 ? 's' : ''));
|
|
576728
577853
|
if (metaInfo.toolCalls) parts.push(metaInfo.toolCalls + ' tool call' + (metaInfo.toolCalls > 1 ? 's' : ''));
|
|
@@ -576735,9 +577860,9 @@ async function sendMessage() {
|
|
|
576735
577860
|
// Collapse tool calls into a dropdown
|
|
576736
577861
|
if (chatTools.length > 0) {
|
|
576737
577862
|
const details = document.createElement('details');
|
|
576738
|
-
details.style.cssText = 'margin:2px 0;font-size:0.65rem;color
|
|
577863
|
+
details.style.cssText = 'margin:2px 0;font-size:0.65rem;color:var(--color-fg-faint)';
|
|
576739
577864
|
const summary = document.createElement('summary');
|
|
576740
|
-
summary.style.cssText = 'cursor:pointer;color
|
|
577865
|
+
summary.style.cssText = 'cursor:pointer;color:var(--color-fg-muted);font-size:0.6rem';
|
|
576741
577866
|
summary.textContent = 'show ' + chatTools.length + ' tool call' + (chatTools.length > 1 ? 's' : '');
|
|
576742
577867
|
details.appendChild(summary);
|
|
576743
577868
|
while (toolsContainer.firstChild) details.appendChild(toolsContainer.firstChild);
|
|
@@ -576756,13 +577881,17 @@ async function sendMessage() {
|
|
|
576756
577881
|
// Match the red left-border styling used by failed tool_result
|
|
576757
577882
|
// and the stop-button. Sits inside the assistant bubble so it
|
|
576758
577883
|
// visually parents to the same turn the user initiated.
|
|
576759
|
-
msgDiv.innerHTML = '<div style="background:#2a1e1e;border-left:3px solid
|
|
577884
|
+
msgDiv.innerHTML = '<div style="background:#2a1e1e;border-left:3px solid var(--color-error);color:var(--color-error);padding:6px 10px 6px 14px;margin:3px 0;font-family:inherit"><div style="color:var(--color-error);font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px">\\u25B8 error</div><div style="color:#ff7777;white-space:pre-wrap;word-break:break-word">' + escHtml(err.message) + '</div></div>';
|
|
576760
577885
|
}
|
|
576761
577886
|
|
|
576762
577887
|
streaming = false;
|
|
576763
577888
|
chatAbortController = null;
|
|
576764
577889
|
document.getElementById('send-btn').style.display = 'inline-block';
|
|
576765
577890
|
document.getElementById('stop-btn').style.display = 'none';
|
|
577891
|
+
// OWUI-3: belt-and-braces — make sure the indicator is gone if we
|
|
577892
|
+
// reach this finally{} via an error path before the success branch
|
|
577893
|
+
// had a chance to call hideStreamingIndicator.
|
|
577894
|
+
try { hideStreamingIndicator(msgDiv); } catch {}
|
|
576766
577895
|
maybeAutoScroll();
|
|
576767
577896
|
}
|
|
576768
577897
|
|
|
@@ -576805,9 +577934,13 @@ function switchTab(tab) {
|
|
|
576805
577934
|
panel.style.display = (tab === 'chat' || tab === 'agent') ? 'flex' : 'block';
|
|
576806
577935
|
}
|
|
576807
577936
|
document.getElementById('footer').style.display = tab === 'chat' ? 'flex' : 'none';
|
|
576808
|
-
document.querySelectorAll('.tab').forEach(t => { t.style.borderBottomColor = 'transparent'; t.style.color = '
|
|
577937
|
+
document.querySelectorAll('.tab').forEach(t => { t.style.borderBottomColor = 'transparent'; t.style.color = 'var(--color-fg-faint)'; });
|
|
576809
577938
|
const active = document.getElementById('tab-' + tab);
|
|
576810
|
-
if (active) { active.style.borderBottomColor = '
|
|
577939
|
+
if (active) { active.style.borderBottomColor = 'var(--color-brand)'; active.style.color = 'var(--color-brand)'; }
|
|
577940
|
+
// OWUI-2: mirror active state on the sidebar nav
|
|
577941
|
+
document.querySelectorAll('#oa-sidebar .sb-nav').forEach(b => {
|
|
577942
|
+
b.classList.toggle('active', b.getAttribute('data-tab') === tab);
|
|
577943
|
+
});
|
|
576811
577944
|
if (tab === 'jobs') loadJobs();
|
|
576812
577945
|
if (tab === 'agent') {
|
|
576813
577946
|
loadProfiles();
|
|
@@ -576832,7 +577965,7 @@ async function loadProjects() {
|
|
|
576832
577965
|
const resp = await fetch('/v1/projects', { headers: headers() });
|
|
576833
577966
|
if (!resp.ok) {
|
|
576834
577967
|
document.getElementById('projects-list').innerHTML =
|
|
576835
|
-
'<div style="color
|
|
577968
|
+
'<div style="color:var(--color-error)">Failed to load projects: HTTP ' + resp.status + '</div>';
|
|
576836
577969
|
return;
|
|
576837
577970
|
}
|
|
576838
577971
|
const data = await resp.json();
|
|
@@ -576856,8 +577989,8 @@ async function loadProjects() {
|
|
|
576856
577989
|
}
|
|
576857
577990
|
if (projects.length === 0) {
|
|
576858
577991
|
document.getElementById('projects-list').innerHTML =
|
|
576859
|
-
'<div style="color
|
|
576860
|
-
'No projects registered yet. Every time you start <code style="color
|
|
577992
|
+
'<div style="color:var(--color-fg-subtle);padding:20px;text-align:center;border:1px dashed var(--color-border);border-radius:4px">' +
|
|
577993
|
+
'No projects registered yet. Every time you start <code style="color:var(--color-brand)">oa</code> in a folder, it will appear here.' +
|
|
576861
577994
|
'</div>';
|
|
576862
577995
|
return;
|
|
576863
577996
|
}
|
|
@@ -576871,17 +578004,17 @@ async function loadProjects() {
|
|
|
576871
578004
|
};
|
|
576872
578005
|
const isCur = (p) => current && current.root === p.root;
|
|
576873
578006
|
const rows = projects.map(p => {
|
|
576874
|
-
const accent = isCur(p) ? '
|
|
578007
|
+
const accent = isCur(p) ? 'var(--color-brand)' : 'var(--color-border)';
|
|
576875
578008
|
const label = (p.name || p.root.split('/').pop() || p.root)
|
|
576876
578009
|
.replace(/</g, '<').replace(/>/g, '>');
|
|
576877
578010
|
const rootDisplay = p.root.replace(/</g, '<').replace(/>/g, '>');
|
|
576878
|
-
const pidInfo = p.pid ? ' <span style="color
|
|
576879
|
-
return '<div class="proj-row" data-root="' + encodeURIComponent(p.root) + '" style="background
|
|
578011
|
+
const pidInfo = p.pid ? ' <span style="color:var(--color-fg-faint)">pid ' + p.pid + '</span>' : '';
|
|
578012
|
+
return '<div class="proj-row" data-root="' + encodeURIComponent(p.root) + '" style="background:var(--color-bg);border-left:2px solid ' + accent + ';padding:8px 12px;margin:6px 0;cursor:pointer;display:flex;justify-content:space-between;align-items:center">' +
|
|
576880
578013
|
'<div>' +
|
|
576881
|
-
'<div style="color:' + (isCur(p) ? '
|
|
576882
|
-
'<div style="color
|
|
578014
|
+
'<div style="color:' + (isCur(p) ? 'var(--color-brand)' : 'var(--color-fg)') + ';font-weight:' + (isCur(p) ? '600' : '400') + '">' + label + (isCur(p) ? ' <span style="color:var(--color-success);font-size:0.6rem">(active)</span>' : '') + '</div>' +
|
|
578015
|
+
'<div style="color:var(--color-fg-subtle);font-size:0.62rem">' + rootDisplay + '</div>' +
|
|
576883
578016
|
'</div>' +
|
|
576884
|
-
'<div style="color
|
|
578017
|
+
'<div style="color:var(--color-fg-faint);font-size:0.62rem;text-align:right">' +
|
|
576885
578018
|
fmtAgo(p.lastSeen) + pidInfo +
|
|
576886
578019
|
'</div>' +
|
|
576887
578020
|
'</div>';
|
|
@@ -576917,7 +578050,7 @@ async function loadProjects() {
|
|
|
576917
578050
|
});
|
|
576918
578051
|
} catch (e) {
|
|
576919
578052
|
document.getElementById('projects-list').innerHTML =
|
|
576920
|
-
'<div style="color
|
|
578053
|
+
'<div style="color:var(--color-error)">Failed to load projects: ' + (e && e.message ? e.message : String(e)) + '</div>';
|
|
576921
578054
|
}
|
|
576922
578055
|
}
|
|
576923
578056
|
window.loadProjects = loadProjects;
|
|
@@ -576938,8 +578071,8 @@ async function loadConfig() {
|
|
|
576938
578071
|
document.getElementById('config-content').innerHTML =
|
|
576939
578072
|
'<table style="width:100%">' +
|
|
576940
578073
|
Object.entries(c).map(([k,v]) =>
|
|
576941
|
-
'<tr style="border-bottom:1px solid
|
|
576942
|
-
'<td style="padding:4px;color
|
|
578074
|
+
'<tr style="border-bottom:1px solid var(--color-bg-input)"><td style="padding:4px;color:var(--color-fg-muted)">' + k + '</td>' +
|
|
578075
|
+
'<td style="padding:4px;color:var(--color-fg)">' + (v === '[redacted]' ? '<span style="color:var(--color-fg-faint)">[redacted]</span>' : String(v)) + '</td></tr>'
|
|
576943
578076
|
).join('') + '</table>';
|
|
576944
578077
|
document.getElementById('config-endpoint').textContent = ep.url + ' (' + (ep.backendType || 'unknown') + ')';
|
|
576945
578078
|
// Populate model switcher
|
|
@@ -576958,11 +578091,11 @@ async function loadConfig() {
|
|
|
576958
578091
|
const r = await fetch('/v1/profiles', { headers: headers() });
|
|
576959
578092
|
const d = await r.json();
|
|
576960
578093
|
document.getElementById('config-profiles').innerHTML = (d.profiles || []).map(p =>
|
|
576961
|
-
'<div style="background
|
|
576962
|
-
'<span style="color
|
|
576963
|
-
(p.encrypted ? ' <span style="color
|
|
576964
|
-
' <span style="color
|
|
576965
|
-
).join('') || '<span style="color
|
|
578094
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:6px 10px;margin:4px 0">' +
|
|
578095
|
+
'<span style="color:var(--color-brand)">' + p.name + '</span>' +
|
|
578096
|
+
(p.encrypted ? ' <span style="color:var(--color-fg-faint);font-size:0.6rem">(encrypted)</span>' : '') +
|
|
578097
|
+
' <span style="color:var(--color-fg-faint);font-size:0.6rem">' + (p.source || '') + '</span></div>'
|
|
578098
|
+
).join('') || '<span style="color:var(--color-fg-faint)">No profiles</span>';
|
|
576966
578099
|
} catch {}
|
|
576967
578100
|
}
|
|
576968
578101
|
|
|
@@ -577015,17 +578148,17 @@ async function loadActivity() {
|
|
|
577015
578148
|
const r = await fetch('/v1/audit?limit=50', { headers: headers() });
|
|
577016
578149
|
const d = await r.json();
|
|
577017
578150
|
const feed = document.getElementById('activity-feed');
|
|
577018
|
-
if (!d.records?.length) { feed.innerHTML = '<span style="color
|
|
578151
|
+
if (!d.records?.length) { feed.innerHTML = '<span style="color:var(--color-fg-faint)">No activity yet</span>'; return; }
|
|
577019
578152
|
feed.innerHTML = d.records.map(r => {
|
|
577020
578153
|
const time = r.ts?.split('T')[1]?.slice(0,8) || '';
|
|
577021
|
-
const color = r.status >= 400 ? '
|
|
577022
|
-
return '<div style="padding:3px 0;border-bottom:1px solid
|
|
577023
|
-
'<span style="color
|
|
578154
|
+
const color = r.status >= 400 ? 'var(--color-error)' : r.status >= 300 ? 'var(--color-brand)' : 'var(--color-success)';
|
|
578155
|
+
return '<div style="padding:3px 0;border-bottom:1px solid var(--color-bg-elevated)">' +
|
|
578156
|
+
'<span style="color:var(--color-fg-faint)">' + time + '</span> ' +
|
|
577024
578157
|
'<span style="color:' + color + '">' + r.status + '</span> ' +
|
|
577025
|
-
'<span style="color
|
|
577026
|
-
'<span style="color
|
|
577027
|
-
'<span style="color
|
|
577028
|
-
'<span style="color
|
|
578158
|
+
'<span style="color:var(--color-fg-muted)">' + r.method + '</span> ' +
|
|
578159
|
+
'<span style="color:var(--color-fg)">' + r.path + '</span> ' +
|
|
578160
|
+
'<span style="color:var(--color-fg-faint)">' + r.latencyMs + 'ms</span> ' +
|
|
578161
|
+
'<span style="color:var(--color-fg-faint);font-size:0.6rem">' + (r.user || '') + '</span></div>';
|
|
577029
578162
|
}).join('');
|
|
577030
578163
|
} catch {}
|
|
577031
578164
|
}
|
|
@@ -577039,15 +578172,15 @@ async function loadDaemons() {
|
|
|
577039
578172
|
const d = await r.json();
|
|
577040
578173
|
const el = document.getElementById('dashboard-daemons');
|
|
577041
578174
|
if (!d.runs?.length) {
|
|
577042
|
-
el.innerHTML = '<div style="background
|
|
578175
|
+
el.innerHTML = '<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;color:var(--color-fg-faint);font-size:0.7rem">No active processes</div>';
|
|
577043
578176
|
return;
|
|
577044
578177
|
}
|
|
577045
|
-
el.innerHTML = '<h3 style="color
|
|
578178
|
+
el.innerHTML = '<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:8px">Active Processes</h3>' +
|
|
577046
578179
|
d.runs.map(j =>
|
|
577047
|
-
'<div style="background
|
|
577048
|
-
'<span style="color
|
|
577049
|
-
'<span style="color
|
|
577050
|
-
'<span style="color
|
|
578180
|
+
'<div style="background:var(--color-bg-elevated);border-left:2px solid var(--color-brand);padding:6px 10px;margin:4px 0;font-size:0.72rem">' +
|
|
578181
|
+
'<span style="color:var(--color-brand)">' + (j.id||'').slice(0,12) + '</span> ' +
|
|
578182
|
+
'<span style="color:var(--color-success)">running</span> ' +
|
|
578183
|
+
'<span style="color:var(--color-fg-muted)">' + (j.task||'').slice(0,50) + '</span></div>'
|
|
577051
578184
|
).join('');
|
|
577052
578185
|
} catch {}
|
|
577053
578186
|
}
|
|
@@ -577061,33 +578194,33 @@ async function loadScheduled() {
|
|
|
577061
578194
|
if (!el) return;
|
|
577062
578195
|
const tasks = Array.isArray(d.tasks) ? d.tasks : [];
|
|
577063
578196
|
if (tasks.length === 0) {
|
|
577064
|
-
el.innerHTML = '<div style="background
|
|
578197
|
+
el.innerHTML = '<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;color:var(--color-fg-faint);font-size:0.7rem">No scheduled tasks found</div>';
|
|
577065
578198
|
return;
|
|
577066
578199
|
}
|
|
577067
578200
|
const rows = tasks.map(t => {
|
|
577068
578201
|
const enabled = !!t.enabled;
|
|
577069
578202
|
const btn = enabled
|
|
577070
|
-
? '<button onclick="toggleScheduled(\\'' + t.id + '\\',false)" style="background
|
|
577071
|
-
: '<button onclick="toggleScheduled(\\'' + t.id + '\\',true)" style="background
|
|
577072
|
-
const color = enabled ? '
|
|
578203
|
+
? '<button onclick="toggleScheduled(\\'' + t.id + '\\',false)" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:2px 6px;border-radius:3px;font-size:0.65rem;cursor:pointer">disable</button>'
|
|
578204
|
+
: '<button onclick="toggleScheduled(\\'' + t.id + '\\',true)" style="background:var(--color-bg-input);border:1px solid #2a3a2a;color:var(--color-success);padding:2px 6px;border-radius:3px;font-size:0.65rem;cursor:pointer">enable</button>';
|
|
578205
|
+
const color = enabled ? 'var(--color-success)' : 'var(--color-error)';
|
|
577073
578206
|
const procInfo = t.procs && t.procs.length ? (' (' + t.procs.length + ' proc)') : '';
|
|
577074
578207
|
const up = t.procs && t.procs[0] && t.procs[0].uptime_s ? (' • up ' + Math.max(1, Math.round(t.procs[0].uptime_s/60)) + 'm') : '';
|
|
577075
|
-
const killBtn = '<button onclick="killScheduledTask(\\'' + t.id + '\\')" style="background
|
|
577076
|
-
const row = '<div style="background
|
|
577077
|
-
+ '<div style="color
|
|
577078
|
-
+ '<div style="color
|
|
578208
|
+
const killBtn = '<button onclick="killScheduledTask(\\'' + t.id + '\\')" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:2px 6px;border-radius:3px;font-size:0.65rem;cursor:pointer">kill</button>';
|
|
578209
|
+
const row = '<div style="background:var(--color-bg-elevated);border-left:2px solid ' + color + ';padding:6px 10px;margin:4px 0;font-size:0.72rem">'
|
|
578210
|
+
+ '<div style="color:var(--color-fg)">' + (t.name || '(task)') + ' <span style="color:var(--color-fg-faint)">' + (t.schedule || '') + '</span>' + procInfo + up + '</div>'
|
|
578211
|
+
+ '<div style="color:var(--color-fg-faint);font-size:0.6rem">' + t.file + '#' + t.index + '</div>'
|
|
577079
578212
|
+ '<div style="margin-top:4px;display:flex;gap:8px">' + btn + killBtn + '</div>'
|
|
577080
578213
|
+ '</div>';
|
|
577081
578214
|
return row;
|
|
577082
578215
|
}).join('');
|
|
577083
|
-
el.innerHTML = '<h3 style="color
|
|
578216
|
+
el.innerHTML = '<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:8px">Scheduled Tasks</h3>' + rows
|
|
577084
578217
|
+ '<div style="margin-top:6px;display:flex;gap:8px">'
|
|
577085
|
-
+ '<button onclick="disableAllScheduled()" title="Disable all scheduled tasks" style="background
|
|
577086
|
-
+ '<button onclick="enableAllScheduled()" title="Enable all scheduled tasks" style="background
|
|
577087
|
-
+ '<button onclick="killScheduled()" title="Kill OA scheduler processes" style="background
|
|
577088
|
-
+ '<button onclick="adoptScheduled()" title="Adopt legacy cron jobs into tasks.json" style="background
|
|
577089
|
-
+ '<button onclick="fixupScheduled()" title="Rewrite cron entries to canonical form" style="background
|
|
577090
|
-
+ '<button onclick="migrateScheduled()" title="Migrate cron → systemd user timers" style="background
|
|
578218
|
+
+ '<button onclick="disableAllScheduled()" title="Disable all scheduled tasks" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:3px 8px;border-radius:3px;font-size:0.65rem;cursor:pointer">disable all</button>'
|
|
578219
|
+
+ '<button onclick="enableAllScheduled()" title="Enable all scheduled tasks" style="background:var(--color-bg-input);border:1px solid #2a3a2a;color:var(--color-success);padding:3px 8px;border-radius:3px;font-size:0.65rem;cursor:pointer">enable all</button>'
|
|
578220
|
+
+ '<button onclick="killScheduled()" title="Kill OA scheduler processes" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:3px 8px;border-radius:3px;font-size:0.65rem;cursor:pointer">kill OA schedulers</button>'
|
|
578221
|
+
+ '<button onclick="adoptScheduled()" title="Adopt legacy cron jobs into tasks.json" style="background:var(--color-bg-input);border:1px solid #2a2a5a;color:#6e7bd9;padding:3px 8px;border-radius:3px;font-size:0.65rem;cursor:pointer">adopt</button>'
|
|
578222
|
+
+ '<button onclick="fixupScheduled()" title="Rewrite cron entries to canonical form" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:#dba15f;padding:3px 8px;border-radius:3px;font-size:0.65rem;cursor:pointer">fixup</button>'
|
|
578223
|
+
+ '<button onclick="migrateScheduled()" title="Migrate cron → systemd user timers" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:#a1db5f;padding:3px 8px;border-radius:3px;font-size:0.65rem;cursor:pointer">migrate</button>'
|
|
577091
578224
|
+ '</div>';
|
|
577092
578225
|
} catch {}
|
|
577093
578226
|
}
|
|
@@ -577234,15 +578367,15 @@ async function loadServices() {
|
|
|
577234
578367
|
const svcs = Array.isArray(d.services) ? d.services : [];
|
|
577235
578368
|
if (!svcs.length) { el.innerHTML = ''; return; }
|
|
577236
578369
|
const rows = svcs.map(s => {
|
|
577237
|
-
const stopBtn = '<button onclick="svcAction(\\'' + s.name + '\\',\\'stop\\')" style="background
|
|
577238
|
-
const disBtn = '<button onclick="svcAction(\\'' + s.name + '\\',\\'disable\\')" style="background
|
|
577239
|
-
return '<div style="background
|
|
577240
|
-
+ '<div style="color
|
|
577241
|
-
+ '<div style="color
|
|
578370
|
+
const stopBtn = '<button onclick="svcAction(\\'' + s.name + '\\',\\'stop\\')" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:2px 6px;border-radius:3px;font-size:0.65rem;cursor:pointer">stop</button>';
|
|
578371
|
+
const disBtn = '<button onclick="svcAction(\\'' + s.name + '\\',\\'disable\\')" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:2px 6px;border-radius:3px;font-size:0.65rem;cursor:pointer">disable</button>';
|
|
578372
|
+
return '<div style="background:var(--color-bg-elevated);border-left:2px solid var(--color-border);padding:6px 10px;margin:4px 0;font-size:0.72rem">'
|
|
578373
|
+
+ '<div style="color:var(--color-fg)">' + s.name + '</div>'
|
|
578374
|
+
+ '<div style="color:var(--color-fg-faint);font-size:0.6rem">enabled: ' + s.enabled + ' • active: ' + s.active + '</div>'
|
|
577242
578375
|
+ '<div style="margin-top:4px;display:flex;gap:8px">' + stopBtn + disBtn + '</div>'
|
|
577243
578376
|
+ '</div>';
|
|
577244
578377
|
}).join('');
|
|
577245
|
-
el.innerHTML = '<h3 style="color
|
|
578378
|
+
el.innerHTML = '<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:8px">Services (systemd --user)</h3>' + rows;
|
|
577246
578379
|
} catch {}
|
|
577247
578380
|
}
|
|
577248
578381
|
|
|
@@ -577334,7 +578467,7 @@ async function submitAgentTask() {
|
|
|
577334
578467
|
div.style.padding = '2px 0';
|
|
577335
578468
|
if (evt.type === 'run_started') {
|
|
577336
578469
|
currentRunId = evt.run_id;
|
|
577337
|
-
div.innerHTML = '<span style="color
|
|
578470
|
+
div.innerHTML = '<span style="color:var(--color-brand)">Task started</span> — run_id: ' + evt.run_id;
|
|
577338
578471
|
// WO-TASK-02: Persist this run into the agent runs storage so
|
|
577339
578472
|
// it appears in the #agent-session-select dropdown immediately.
|
|
577340
578473
|
try {
|
|
@@ -577350,12 +578483,12 @@ async function submitAgentTask() {
|
|
|
577350
578483
|
currentAgentRunId = evt.run_id;
|
|
577351
578484
|
} catch {}
|
|
577352
578485
|
} else if (evt.type === 'run_completed') {
|
|
577353
|
-
div.innerHTML = '<span style="color:' + (evt.exit_code === 0 ? '
|
|
578486
|
+
div.innerHTML = '<span style="color:' + (evt.exit_code === 0 ? 'var(--color-success)' : 'var(--color-error)') + '">Task ' + (evt.exit_code === 0 ? 'completed' : 'failed') + '</span> — exit: ' + evt.exit_code;
|
|
577354
578487
|
document.getElementById('agent-submit').style.display = 'inline-block';
|
|
577355
578488
|
document.getElementById('agent-abort').style.display = 'none';
|
|
577356
578489
|
currentRunId = null;
|
|
577357
578490
|
} else if (evt.type === 'stdout') {
|
|
577358
|
-
div.style.color = '
|
|
578491
|
+
div.style.color = 'var(--color-fg-muted)';
|
|
577359
578492
|
div.style.fontFamily = 'inherit';
|
|
577360
578493
|
div.textContent = evt.data?.slice?.(0, 200) || '';
|
|
577361
578494
|
}
|
|
@@ -577365,7 +578498,7 @@ async function submitAgentTask() {
|
|
|
577365
578498
|
}
|
|
577366
578499
|
}
|
|
577367
578500
|
} catch (err) {
|
|
577368
|
-
eventsDiv.innerHTML += '<div style="background:#2a1e1e;border-left:3px solid
|
|
578501
|
+
eventsDiv.innerHTML += '<div style="background:#2a1e1e;border-left:3px solid var(--color-error);color:#ff7777;padding:6px 10px 6px 14px;margin:3px 0"><div style="color:var(--color-error);font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px">\\u25B8 error</div>' + escHtml(err.message) + '</div>';
|
|
577369
578502
|
}
|
|
577370
578503
|
document.getElementById('agent-submit').style.display = 'inline-block';
|
|
577371
578504
|
document.getElementById('agent-abort').style.display = 'none';
|
|
@@ -577382,15 +578515,15 @@ async function loadDashboard() {
|
|
|
577382
578515
|
const r = await fetch('/health', { headers: headers() });
|
|
577383
578516
|
const d = await r.json();
|
|
577384
578517
|
document.getElementById('dashboard-health').innerHTML =
|
|
577385
|
-
'<div style="background
|
|
577386
|
-
'<div style="color
|
|
577387
|
-
'<div style="color
|
|
577388
|
-
'<div style="background
|
|
577389
|
-
'<div style="color
|
|
577390
|
-
'<div style="color
|
|
577391
|
-
'<div style="background
|
|
577392
|
-
'<div style="color
|
|
577393
|
-
'<div style="color
|
|
578518
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
578519
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">STATUS</div>' +
|
|
578520
|
+
'<div style="color:var(--color-success);font-size:0.8rem">' + d.status + '</div></div>' +
|
|
578521
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
578522
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">UPTIME</div>' +
|
|
578523
|
+
'<div style="color:var(--color-brand);font-size:0.8rem">' + Math.floor(d.uptime_s/60) + 'm</div></div>' +
|
|
578524
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
578525
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">VERSION</div>' +
|
|
578526
|
+
'<div style="color:var(--color-fg);font-size:0.8rem">' + d.version + '</div></div>';
|
|
577394
578527
|
} catch {}
|
|
577395
578528
|
// System info + model recommendations
|
|
577396
578529
|
try {
|
|
@@ -577399,15 +578532,15 @@ async function loadDashboard() {
|
|
|
577399
578532
|
const gpuHtml = (sys.gpu || []).map(g => g.name + ' (' + g.vram_gb + 'GB)').join(', ') || 'No GPU detected';
|
|
577400
578533
|
const healthEl = document.getElementById('dashboard-health');
|
|
577401
578534
|
healthEl.innerHTML +=
|
|
577402
|
-
'<div style="background
|
|
577403
|
-
'<div style="color
|
|
577404
|
-
'<div style="color
|
|
577405
|
-
'<div style="background
|
|
577406
|
-
'<div style="color
|
|
577407
|
-
'<div style="color
|
|
577408
|
-
'<div style="background
|
|
577409
|
-
'<div style="color
|
|
577410
|
-
'<div style="color
|
|
578535
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
578536
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">GPU</div>' +
|
|
578537
|
+
'<div style="color:var(--color-fg);font-size:0.7rem">' + gpuHtml + '</div></div>' +
|
|
578538
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
578539
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">RAM</div>' +
|
|
578540
|
+
'<div style="color:var(--color-fg);font-size:0.8rem">' + sys.ram_gb + 'GB</div></div>' +
|
|
578541
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1;min-width:120px">' +
|
|
578542
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">MAX MODEL</div>' +
|
|
578543
|
+
'<div style="color:var(--color-brand);font-size:0.8rem">' + sys.recommended_max_params + '</div></div>';
|
|
577411
578544
|
// Store for model badges
|
|
577412
578545
|
window._sysMaxParams = sys.recommended_max_params;
|
|
577413
578546
|
} catch {}
|
|
@@ -577420,30 +578553,30 @@ async function loadDashboard() {
|
|
|
577420
578553
|
for (const [label, stats] of Object.entries(ps)) {
|
|
577421
578554
|
const s = stats;
|
|
577422
578555
|
providerCards +=
|
|
577423
|
-
'<div style="background
|
|
577424
|
-
'<div style="color
|
|
578556
|
+
'<div style="background:var(--color-bg-elevated);border-left:2px solid var(--color-brand);padding:8px 12px;margin:4px 0">' +
|
|
578557
|
+
'<div style="color:var(--color-brand);font-size:0.7rem;font-weight:bold">' + label + '</div>' +
|
|
577425
578558
|
'<div style="display:flex;gap:16px;margin-top:4px">' +
|
|
577426
|
-
'<span style="color
|
|
577427
|
-
'<span style="color
|
|
577428
|
-
'<span style="color
|
|
578559
|
+
'<span style="color:var(--color-fg-faint);font-size:0.65rem">in: <span style="color:var(--color-fg)">' + (s.tokensIn || 0).toLocaleString() + '</span></span>' +
|
|
578560
|
+
'<span style="color:var(--color-fg-faint);font-size:0.65rem">out: <span style="color:var(--color-fg)">' + (s.tokensOut || 0).toLocaleString() + '</span></span>' +
|
|
578561
|
+
'<span style="color:var(--color-fg-faint);font-size:0.65rem">reqs: <span style="color:var(--color-fg)">' + (s.requests || 0).toLocaleString() + '</span></span>' +
|
|
577429
578562
|
'</div></div>';
|
|
577430
578563
|
}
|
|
577431
578564
|
const totalIn = d.persistent?.totalIn || d.totalTokensIn || 0;
|
|
577432
578565
|
const totalOut = d.persistent?.totalOut || d.totalTokensOut || 0;
|
|
577433
578566
|
document.getElementById('dashboard-usage').innerHTML =
|
|
577434
|
-
'<h3 style="color
|
|
578567
|
+
'<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:8px">Token Usage by Provider (persistent)</h3>' +
|
|
577435
578568
|
'<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:8px">' +
|
|
577436
|
-
'<div style="background
|
|
577437
|
-
'<div style="color
|
|
577438
|
-
'<div style="color
|
|
577439
|
-
'<div style="background
|
|
577440
|
-
'<div style="color
|
|
577441
|
-
'<div style="color
|
|
577442
|
-
'<div style="background
|
|
577443
|
-
'<div style="color
|
|
577444
|
-
'<div style="color
|
|
578569
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1">' +
|
|
578570
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">TOTAL IN</div>' +
|
|
578571
|
+
'<div style="color:var(--color-brand);font-size:0.9rem;font-weight:bold">' + totalIn.toLocaleString() + '</div></div>' +
|
|
578572
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1">' +
|
|
578573
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">TOTAL OUT</div>' +
|
|
578574
|
+
'<div style="color:var(--color-brand);font-size:0.9rem;font-weight:bold">' + totalOut.toLocaleString() + '</div></div>' +
|
|
578575
|
+
'<div style="background:var(--color-bg-elevated);border:1px solid var(--color-bg-input);border-radius:3px;padding:8px 12px;flex:1">' +
|
|
578576
|
+
'<div style="color:var(--color-fg-faint);font-size:0.6rem">TOTAL REQUESTS</div>' +
|
|
578577
|
+
'<div style="color:var(--color-brand);font-size:0.9rem;font-weight:bold">' + (d.persistent?.totalRequests || 0).toLocaleString() + '</div></div>' +
|
|
577445
578578
|
'</div>' +
|
|
577446
|
-
(providerCards || '<div style="color
|
|
578579
|
+
(providerCards || '<div style="color:var(--color-fg-faint);font-size:0.7rem">No provider usage recorded yet</div>');
|
|
577447
578580
|
} catch {}
|
|
577448
578581
|
}
|
|
577449
578582
|
|
|
@@ -577454,20 +578587,20 @@ async function loadJobs() {
|
|
|
577454
578587
|
try {
|
|
577455
578588
|
const r = await fetch('/v1/runs', { headers: headers() });
|
|
577456
578589
|
const d = await r.json();
|
|
577457
|
-
if (!d.runs?.length) { list.innerHTML = '<div style="color
|
|
578590
|
+
if (!d.runs?.length) { list.innerHTML = '<div style="color:var(--color-fg-faint)">No jobs yet</div>'; return; }
|
|
577458
578591
|
let html = '<table style="width:100%;border-collapse:collapse">';
|
|
577459
|
-
html += '<tr style="color
|
|
578592
|
+
html += '<tr style="color:var(--color-brand);font-size:0.65rem"><th style="text-align:left;padding:4px">ID</th><th>Status</th><th>Task</th><th>Duration</th></tr>';
|
|
577460
578593
|
for (const j of d.runs.slice(0, 20)) {
|
|
577461
|
-
const color = j.status === 'completed' ? '
|
|
578594
|
+
const color = j.status === 'completed' ? 'var(--color-success)' : j.status === 'running' ? 'var(--color-brand)' : 'var(--color-error)';
|
|
577462
578595
|
const dur = j.durationMs ? (j.durationMs / 1000).toFixed(1) + 's' : '—';
|
|
577463
|
-
html += '<tr style="border-top:1px solid
|
|
578596
|
+
html += '<tr style="border-top:1px solid var(--color-bg-input)"><td style="padding:4px;color:var(--color-fg-muted)">' + (j.id||'').slice(0,12) + '</td>';
|
|
577464
578597
|
html += '<td style="color:' + color + '">' + (j.status||'?') + '</td>';
|
|
577465
|
-
html += '<td style="color
|
|
577466
|
-
html += '<td style="color
|
|
578598
|
+
html += '<td style="color:var(--color-fg);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml((j.task||'').slice(0,60)) + '</td>';
|
|
578599
|
+
html += '<td style="color:var(--color-fg-faint)">' + dur + '</td></tr>';
|
|
577467
578600
|
}
|
|
577468
578601
|
html += '</table>';
|
|
577469
578602
|
list.innerHTML = html;
|
|
577470
|
-
} catch { list.innerHTML = '<div style="color
|
|
578603
|
+
} catch { list.innerHTML = '<div style="color:var(--color-error)">Failed to load jobs</div>'; }
|
|
577471
578604
|
}
|
|
577472
578605
|
|
|
577473
578606
|
// Session storage (localStorage for persistence across page reloads)
|
|
@@ -577694,7 +578827,7 @@ function switchSession(id) {
|
|
|
577694
578827
|
// Restore metadata bar
|
|
577695
578828
|
if (m.meta && m.role === 'assistant') {
|
|
577696
578829
|
const metaBar = document.createElement('div');
|
|
577697
|
-
metaBar.style.cssText = 'margin:6px 0 2px;padding:4px 8px;background
|
|
578830
|
+
metaBar.style.cssText = 'margin:6px 0 2px;padding:4px 8px;background:var(--color-bg-elevated);border-radius:3px;font-size:0.6rem;color:var(--color-fg-faint);display:flex;gap:12px';
|
|
577698
578831
|
const parts = [];
|
|
577699
578832
|
if (m.meta.turns) parts.push(m.meta.turns + ' turns');
|
|
577700
578833
|
if (m.meta.toolCalls) parts.push(m.meta.toolCalls + ' tool calls');
|
|
@@ -577705,24 +578838,24 @@ function switchSession(id) {
|
|
|
577705
578838
|
// Restore tool call provenance with expandable args
|
|
577706
578839
|
if (m.tools?.length && m.role === 'assistant') {
|
|
577707
578840
|
const outerDetails = document.createElement('details');
|
|
577708
|
-
outerDetails.style.cssText = 'margin:2px 0;font-size:0.6rem;color
|
|
578841
|
+
outerDetails.style.cssText = 'margin:2px 0;font-size:0.6rem;color:var(--color-fg-faint)';
|
|
577709
578842
|
const outerSummary = document.createElement('summary');
|
|
577710
|
-
outerSummary.style.cssText = 'cursor:pointer;color
|
|
578843
|
+
outerSummary.style.cssText = 'cursor:pointer;color:var(--color-fg-muted)';
|
|
577711
578844
|
outerSummary.textContent = 'show ' + m.tools.length + ' tool calls';
|
|
577712
578845
|
outerDetails.appendChild(outerSummary);
|
|
577713
578846
|
for (const t of m.tools) {
|
|
577714
578847
|
const toolObj = typeof t === 'string' ? { tool: t } : t;
|
|
577715
578848
|
const td = document.createElement('details');
|
|
577716
|
-
td.style.cssText = 'background
|
|
578849
|
+
td.style.cssText = 'background:var(--color-bg-elevated);border-left:2px solid var(--color-brand);margin:2px 0';
|
|
577717
578850
|
const ts = document.createElement('summary');
|
|
577718
|
-
ts.style.cssText = 'padding:4px 8px;color
|
|
578851
|
+
ts.style.cssText = 'padding:4px 8px;color:var(--color-brand);cursor:pointer;font-size:0.65rem';
|
|
577719
578852
|
ts.textContent = toolObj.tool || String(t);
|
|
577720
578853
|
td.appendChild(ts);
|
|
577721
578854
|
if (toolObj.args) {
|
|
577722
578855
|
const ad = document.createElement('div');
|
|
577723
|
-
ad.style.cssText = 'padding:4px 8px 6px 16px;color
|
|
578856
|
+
ad.style.cssText = 'padding:4px 8px 6px 16px;color:var(--color-fg-muted);font-size:0.6rem';
|
|
577724
578857
|
for (const [k, v] of Object.entries(toolObj.args)) {
|
|
577725
|
-
ad.innerHTML += '<div style="padding:1px 0"><span style="color
|
|
578858
|
+
ad.innerHTML += '<div style="padding:1px 0"><span style="color:var(--color-brand)">' + k + ':</span> ' + String(v).slice(0, 200) + '</div>';
|
|
577726
578859
|
}
|
|
577727
578860
|
td.appendChild(ad);
|
|
577728
578861
|
}
|
|
@@ -577808,13 +578941,13 @@ async function loadAgentWorkspaceRoot() {
|
|
|
577808
578941
|
const rootPath = d.path || '.';
|
|
577809
578942
|
cwdEl.innerHTML = '';
|
|
577810
578943
|
const wsSpan = document.createElement('span');
|
|
577811
|
-
wsSpan.style.cssText = 'color
|
|
578944
|
+
wsSpan.style.cssText = 'color:var(--color-fg-muted)';
|
|
577812
578945
|
wsSpan.textContent = 'ROOT: ' + rootPath;
|
|
577813
578946
|
cwdEl.appendChild(wsSpan);
|
|
577814
578947
|
cwdEl.appendChild(document.createElement('br'));
|
|
577815
578948
|
const wdSpan = document.createElement('span');
|
|
577816
578949
|
wdSpan.id = 'agent-workspace-dir-label';
|
|
577817
|
-
wdSpan.style.cssText = 'color
|
|
578950
|
+
wdSpan.style.cssText = 'color:var(--color-brand)';
|
|
577818
578951
|
wdSpan.textContent = 'CWD: ' + (agentWorkingDir || rootPath);
|
|
577819
578952
|
cwdEl.appendChild(wdSpan);
|
|
577820
578953
|
|
|
@@ -577824,7 +578957,7 @@ async function loadAgentWorkspaceRoot() {
|
|
|
577824
578957
|
renderAgentWorkspaceTree();
|
|
577825
578958
|
} catch {
|
|
577826
578959
|
const tree = document.getElementById('agent-workspace-tree');
|
|
577827
|
-
if (tree) tree.innerHTML = '<div style="color
|
|
578960
|
+
if (tree) tree.innerHTML = '<div style="color:var(--color-fg-faint)">Could not load files</div>';
|
|
577828
578961
|
}
|
|
577829
578962
|
}
|
|
577830
578963
|
|
|
@@ -577855,7 +578988,7 @@ function renderAgentTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
577855
578988
|
|
|
577856
578989
|
if (e.type === 'dir') {
|
|
577857
578990
|
const caret = document.createElement('span');
|
|
577858
|
-
caret.style.cssText = 'color
|
|
578991
|
+
caret.style.cssText = 'color:var(--color-fg-subtle); font-size:0.55rem; width:10px; display:inline-block; text-align:center';
|
|
577859
578992
|
caret.textContent = agentTreeExpanded.has(childAbs) ? '▾' : '▸';
|
|
577860
578993
|
row.appendChild(caret);
|
|
577861
578994
|
const icon = document.createElement('span');
|
|
@@ -577863,7 +578996,7 @@ function renderAgentTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
577863
578996
|
row.appendChild(icon);
|
|
577864
578997
|
const name = document.createElement('span');
|
|
577865
578998
|
name.textContent = e.name;
|
|
577866
|
-
name.style.cssText = 'color
|
|
578999
|
+
name.style.cssText = 'color:var(--color-brand)';
|
|
577867
579000
|
if (agentWorkingDir && childAbs === agentWorkingDir) {
|
|
577868
579001
|
name.style.cssText += ';font-weight:bold;text-decoration:underline';
|
|
577869
579002
|
}
|
|
@@ -577907,8 +579040,8 @@ function renderAgentTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
577907
579040
|
const name = document.createElement('span');
|
|
577908
579041
|
name.textContent = e.name;
|
|
577909
579042
|
name.style.cssText = agentContextFiles.includes(childAbs)
|
|
577910
|
-
? 'color
|
|
577911
|
-
: 'color
|
|
579043
|
+
? 'color:var(--color-brand);font-weight:bold'
|
|
579044
|
+
: 'color:var(--color-fg)';
|
|
577912
579045
|
row.appendChild(name);
|
|
577913
579046
|
// Click toggles file in/out of agent context
|
|
577914
579047
|
row.addEventListener('click', () => {
|
|
@@ -577947,13 +579080,13 @@ async function loadWorkspaceRoot() {
|
|
|
577947
579080
|
const rootPath = d.path || '.';
|
|
577948
579081
|
cwdEl.innerHTML = '';
|
|
577949
579082
|
const wsSpan = document.createElement('span');
|
|
577950
|
-
wsSpan.style.cssText = 'color
|
|
579083
|
+
wsSpan.style.cssText = 'color:var(--color-fg-muted)';
|
|
577951
579084
|
wsSpan.textContent = 'ROOT: ' + rootPath;
|
|
577952
579085
|
cwdEl.appendChild(wsSpan);
|
|
577953
579086
|
cwdEl.appendChild(document.createElement('br'));
|
|
577954
579087
|
const wdSpan = document.createElement('span');
|
|
577955
579088
|
wdSpan.id = 'workspace-dir-label';
|
|
577956
|
-
wdSpan.style.cssText = 'color
|
|
579089
|
+
wdSpan.style.cssText = 'color:var(--color-brand)';
|
|
577957
579090
|
wdSpan.textContent = 'CWD: ' + (chatWorkingDir || rootPath);
|
|
577958
579091
|
cwdEl.appendChild(wdSpan);
|
|
577959
579092
|
|
|
@@ -577964,7 +579097,7 @@ async function loadWorkspaceRoot() {
|
|
|
577964
579097
|
treeRoot = rootPath;
|
|
577965
579098
|
renderWorkspaceTree();
|
|
577966
579099
|
} catch {
|
|
577967
|
-
document.getElementById('workspace-tree').innerHTML = '<div style="color
|
|
579100
|
+
document.getElementById('workspace-tree').innerHTML = '<div style="color:var(--color-fg-faint)">Could not load files</div>';
|
|
577968
579101
|
}
|
|
577969
579102
|
}
|
|
577970
579103
|
|
|
@@ -578000,7 +579133,7 @@ function renderTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
578000
579133
|
if (e.type === 'dir') {
|
|
578001
579134
|
const caret = document.createElement('span');
|
|
578002
579135
|
caret.className = 'tree-caret';
|
|
578003
|
-
caret.style.cssText = 'color
|
|
579136
|
+
caret.style.cssText = 'color:var(--color-fg-subtle); font-size:0.55rem; width:10px; display:inline-block; text-align:center';
|
|
578004
579137
|
caret.textContent = treeExpanded.has(childAbs) ? '▾' : '▸';
|
|
578005
579138
|
row.appendChild(caret);
|
|
578006
579139
|
const icon = document.createElement('span');
|
|
@@ -578008,7 +579141,7 @@ function renderTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
578008
579141
|
row.appendChild(icon);
|
|
578009
579142
|
const name = document.createElement('span');
|
|
578010
579143
|
name.textContent = e.name;
|
|
578011
|
-
name.style.cssText = 'color
|
|
579144
|
+
name.style.cssText = 'color:var(--color-brand)';
|
|
578012
579145
|
// Highlight the current workspace
|
|
578013
579146
|
if (chatWorkingDir && childAbs === chatWorkingDir) {
|
|
578014
579147
|
name.style.cssText += ';font-weight:bold;text-decoration:underline';
|
|
@@ -578047,7 +579180,7 @@ function renderTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
578047
579180
|
name.textContent = e.name;
|
|
578048
579181
|
name.style.cssText = contextFiles.includes(childAbs)
|
|
578049
579182
|
? 'color:#4e94c9;font-weight:bold'
|
|
578050
|
-
: 'color
|
|
579183
|
+
: 'color:var(--color-fg)';
|
|
578051
579184
|
row.appendChild(name);
|
|
578052
579185
|
row.addEventListener('click', (ev) => {
|
|
578053
579186
|
ev.stopPropagation();
|
|
@@ -578078,7 +579211,7 @@ function showTreeContextMenu(x, y, path, type) {
|
|
|
578078
579211
|
if (!treeMenuEl) {
|
|
578079
579212
|
treeMenuEl = document.createElement('div');
|
|
578080
579213
|
treeMenuEl.id = 'tree-menu';
|
|
578081
|
-
treeMenuEl.style.cssText = 'position:fixed; background
|
|
579214
|
+
treeMenuEl.style.cssText = 'position:fixed; background:var(--color-bg-elevated); border:1px solid var(--color-brand); border-radius:4px; padding:4px 0; box-shadow:0 4px 20px rgba(0,0,0,0.6); z-index:200; font-size:0.7rem; min-width:180px';
|
|
578082
579215
|
document.body.appendChild(treeMenuEl);
|
|
578083
579216
|
document.addEventListener('click', () => { if (treeMenuEl) treeMenuEl.style.display = 'none'; });
|
|
578084
579217
|
}
|
|
@@ -578098,9 +579231,9 @@ function showTreeContextMenu(x, y, path, type) {
|
|
|
578098
579231
|
for (const it of items) {
|
|
578099
579232
|
if (it.show === false) continue;
|
|
578100
579233
|
const row = document.createElement('div');
|
|
578101
|
-
row.style.cssText = 'padding:6px 16px; cursor:pointer; color
|
|
579234
|
+
row.style.cssText = 'padding:6px 16px; cursor:pointer; color:var(--color-fg)';
|
|
578102
579235
|
row.textContent = it.label;
|
|
578103
|
-
row.addEventListener('mouseenter', () => row.style.background = '
|
|
579236
|
+
row.addEventListener('mouseenter', () => row.style.background = 'var(--color-bg-input)');
|
|
578104
579237
|
row.addEventListener('mouseleave', () => row.style.background = '');
|
|
578105
579238
|
row.addEventListener('click', (ev) => {
|
|
578106
579239
|
ev.stopPropagation();
|
|
@@ -578125,7 +579258,7 @@ function setChatWorkingDir(path) {
|
|
|
578125
579258
|
if (s) {
|
|
578126
579259
|
const old = s.textContent;
|
|
578127
579260
|
s.textContent = path ? 'workspace → ' + path.split('/').slice(-2).join('/') : 'workspace cleared';
|
|
578128
|
-
s.style.color = '
|
|
579261
|
+
s.style.color = 'var(--color-success)';
|
|
578129
579262
|
setTimeout(() => { s.textContent = old; s.style.color = ''; }, 3000);
|
|
578130
579263
|
}
|
|
578131
579264
|
// Also push to the Agent tab input if visible
|
|
@@ -578192,18 +579325,18 @@ async function previewFile(path) {
|
|
|
578192
579325
|
modal.style.cssText = 'position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.8); z-index:300; display:flex; align-items:center; justify-content:center; padding:40px';
|
|
578193
579326
|
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
|
|
578194
579327
|
const card = document.createElement('div');
|
|
578195
|
-
card.style.cssText = 'background
|
|
579328
|
+
card.style.cssText = 'background:var(--color-bg-elevated); border:1px solid var(--color-bg-input); border-radius:4px; max-width:90vw; max-height:90vh; overflow:hidden; display:flex; flex-direction:column';
|
|
578196
579329
|
const title = document.createElement('div');
|
|
578197
|
-
title.style.cssText = 'padding:10px 16px; border-bottom:1px solid
|
|
579330
|
+
title.style.cssText = 'padding:10px 16px; border-bottom:1px solid var(--color-bg-input); color:var(--color-brand); font-size:0.75rem; display:flex; justify-content:space-between; align-items:center; gap:16px';
|
|
578198
579331
|
title.innerHTML = '<span>' + escHtml(path) + '</span>';
|
|
578199
579332
|
const closeBtn = document.createElement('button');
|
|
578200
579333
|
closeBtn.textContent = '×';
|
|
578201
|
-
closeBtn.style.cssText = 'background:none; border:none; color
|
|
579334
|
+
closeBtn.style.cssText = 'background:none; border:none; color:var(--color-fg-subtle); font-size:1.2rem; cursor:pointer';
|
|
578202
579335
|
closeBtn.onclick = () => modal.remove();
|
|
578203
579336
|
title.appendChild(closeBtn);
|
|
578204
579337
|
card.appendChild(title);
|
|
578205
579338
|
const pre = document.createElement('pre');
|
|
578206
|
-
pre.style.cssText = 'padding:12px 16px; color
|
|
579339
|
+
pre.style.cssText = 'padding:12px 16px; color:var(--color-fg); overflow:auto; font-size:0.7rem; margin:0; background:var(--color-bg)';
|
|
578207
579340
|
pre.textContent = d.content || '(empty)';
|
|
578208
579341
|
card.appendChild(pre);
|
|
578209
579342
|
modal.appendChild(card);
|
|
@@ -578223,7 +579356,7 @@ function toggleSandbox() {
|
|
|
578223
579356
|
const btn = document.getElementById('sandbox-toggle');
|
|
578224
579357
|
btn.textContent = 'sandbox: ' + (sandboxMode === 'none' ? 'off' : 'docker');
|
|
578225
579358
|
btn.style.opacity = sandboxMode === 'none' ? '0.5' : '1';
|
|
578226
|
-
btn.style.borderColor = sandboxMode === 'none' ? '
|
|
579359
|
+
btn.style.borderColor = sandboxMode === 'none' ? 'var(--color-border)' : 'var(--color-brand)';
|
|
578227
579360
|
}
|
|
578228
579361
|
|
|
578229
579362
|
// Live system metrics polling
|
|
@@ -578387,8 +579520,8 @@ async function doUpdate() {
|
|
|
578387
579520
|
|
|
578388
579521
|
btn.textContent = 'updated v' + newVersion;
|
|
578389
579522
|
btn.style.background = '#1a3a1a';
|
|
578390
|
-
btn.style.borderColor = '
|
|
578391
|
-
btn.style.color = '
|
|
579523
|
+
btn.style.borderColor = 'var(--color-success)';
|
|
579524
|
+
btn.style.color = 'var(--color-success)';
|
|
578392
579525
|
try { seenVersion = newVersion || seenVersion; } catch {}
|
|
578393
579526
|
|
|
578394
579527
|
// Flash status bar
|
|
@@ -578396,7 +579529,7 @@ async function doUpdate() {
|
|
|
578396
579529
|
if (statusEl) {
|
|
578397
579530
|
const origStatusColor = statusEl.style.color;
|
|
578398
579531
|
statusEl.textContent = 'updated → v' + newVersion;
|
|
578399
|
-
statusEl.style.color = '
|
|
579532
|
+
statusEl.style.color = 'var(--color-success)';
|
|
578400
579533
|
setTimeout(() => { statusEl.style.color = origStatusColor; }, 5000);
|
|
578401
579534
|
}
|
|
578402
579535
|
|
|
@@ -578606,7 +579739,7 @@ function renderInlineError(message) {
|
|
|
578606
579739
|
}
|
|
578607
579740
|
const wrap = document.createElement('div');
|
|
578608
579741
|
wrap.className = 'msg assistant';
|
|
578609
|
-
wrap.innerHTML = '<div style="background:#2a1e1e;border-left:3px solid
|
|
579742
|
+
wrap.innerHTML = '<div style="background:#2a1e1e;border-left:3px solid var(--color-error);color:#ff7777;padding:6px 10px 6px 14px;margin:3px 0;font-family:inherit"><div style="color:var(--color-error);font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px">\\u25B8 error</div><div style="color:#ff7777;white-space:pre-wrap;word-break:break-word">' + escHtml(message) + '</div></div>';
|
|
578610
579743
|
conv.appendChild(wrap);
|
|
578611
579744
|
maybeAutoScroll();
|
|
578612
579745
|
}
|
|
@@ -578702,7 +579835,7 @@ function renderTasksRow(todos) {
|
|
|
578702
579835
|
const total = todos.length;
|
|
578703
579836
|
const counter = document.createElement('span');
|
|
578704
579837
|
counter.className = 'tasks-label';
|
|
578705
|
-
counter.style.color = completed === total ? '#5fa55f' : '
|
|
579838
|
+
counter.style.color = completed === total ? '#5fa55f' : 'var(--color-brand)';
|
|
578706
579839
|
counter.textContent = completed + '/' + total;
|
|
578707
579840
|
tasksRowEl.appendChild(counter);
|
|
578708
579841
|
// One pill per task — color-coded by status
|
|
@@ -578750,13 +579883,13 @@ async function refreshTodos(sessionId) {
|
|
|
578750
579883
|
const li = document.createElement('li');
|
|
578751
579884
|
li.style.cssText = 'padding:2px 0;display:flex;gap:8px;align-items:flex-start';
|
|
578752
579885
|
const mark = document.createElement('span');
|
|
578753
|
-
mark.style.cssText = 'color
|
|
579886
|
+
mark.style.cssText = 'color:var(--color-brand);font-family:monospace;flex-shrink:0';
|
|
578754
579887
|
mark.textContent = statusMark(t.status);
|
|
578755
579888
|
li.appendChild(mark);
|
|
578756
579889
|
const content = document.createElement('span');
|
|
578757
579890
|
content.style.cssText = t.status === 'completed'
|
|
578758
|
-
? 'color
|
|
578759
|
-
: 'color
|
|
579891
|
+
? 'color:var(--color-fg-subtle);text-decoration:line-through'
|
|
579892
|
+
: 'color:var(--color-fg)';
|
|
578760
579893
|
content.textContent = t.content + (t.blocker ? ' (blocked: ' + t.blocker + ')' : '');
|
|
578761
579894
|
li.appendChild(content);
|
|
578762
579895
|
todoListEl.appendChild(li);
|
|
@@ -579043,9 +580176,9 @@ async function refreshVoiceState() {
|
|
|
579043
580176
|
const pill = document.getElementById('voice-state-pill');
|
|
579044
580177
|
if (pill) {
|
|
579045
580178
|
pill.textContent = data.state;
|
|
579046
|
-
pill.style.borderLeftColor = data.state === 'listening' ? '
|
|
579047
|
-
: data.state === 'speaking' ? '
|
|
579048
|
-
: data.state === 'error' ? '
|
|
580179
|
+
pill.style.borderLeftColor = data.state === 'listening' ? 'var(--color-success)'
|
|
580180
|
+
: data.state === 'speaking' ? 'var(--color-brand)'
|
|
580181
|
+
: data.state === 'error' ? 'var(--color-error)' : 'var(--color-fg-faint)';
|
|
579049
580182
|
}
|
|
579050
580183
|
} catch {}
|
|
579051
580184
|
}
|
|
@@ -579087,33 +580220,33 @@ async function loadCloneRefs() {
|
|
|
579087
580220
|
if (!list) return;
|
|
579088
580221
|
try {
|
|
579089
580222
|
const r = await fetch('/v1/voice/clone-refs', { headers: headers() });
|
|
579090
|
-
if (!r.ok) { list.innerHTML = '<div style="color
|
|
580223
|
+
if (!r.ok) { list.innerHTML = '<div style="color:var(--color-fg-subtle)">failed to load refs</div>'; return; }
|
|
579091
580224
|
const data = await r.json();
|
|
579092
580225
|
const refs = data.refs || [];
|
|
579093
580226
|
if (refs.length === 0) {
|
|
579094
|
-
list.innerHTML = '<div style="color
|
|
580227
|
+
list.innerHTML = '<div style="color:var(--color-fg-subtle);padding:14px;text-align:center;border:1px dashed var(--color-border);border-radius:4px">No clone references yet. Upload a clean 3+ second voice sample to clone it.</div>';
|
|
579095
580228
|
return;
|
|
579096
580229
|
}
|
|
579097
580230
|
list.innerHTML = refs.map(ref => {
|
|
579098
|
-
const accent = ref.isActive ? '
|
|
579099
|
-
const statusBadge = ref.isActive ? ' <span style="color
|
|
580231
|
+
const accent = ref.isActive ? 'var(--color-brand)' : 'var(--color-border)';
|
|
580232
|
+
const statusBadge = ref.isActive ? ' <span style="color:var(--color-success);font-size:0.6rem">(active)</span>' : '';
|
|
579100
580233
|
const sizeKb = (ref.size / 1024).toFixed(1);
|
|
579101
580234
|
const fnAttr = escAttr(ref.filename);
|
|
579102
580235
|
const nameAttr = escAttr(ref.name);
|
|
579103
|
-
return '<div style="background
|
|
580236
|
+
return '<div style="background:var(--color-bg);border-left:2px solid ' + accent + ';padding:8px 12px;margin:6px 0;display:flex;justify-content:space-between;align-items:center">' +
|
|
579104
580237
|
'<div>' +
|
|
579105
|
-
'<div style="color:' + (ref.isActive ? '
|
|
579106
|
-
'<div style="color
|
|
580238
|
+
'<div style="color:' + (ref.isActive ? 'var(--color-brand)' : 'var(--color-fg)') + ';font-weight:' + (ref.isActive ? '600' : '400') + '">' + escHtml(ref.name) + statusBadge + '</div>' +
|
|
580239
|
+
'<div style="color:var(--color-fg-subtle);font-size:0.6rem">' + escHtml(ref.filename) + ' · ' + sizeKb + ' KB</div>' +
|
|
579107
580240
|
'</div>' +
|
|
579108
580241
|
'<div style="display:flex;gap:6px">' +
|
|
579109
|
-
(ref.isActive ? '' : '<button onclick="activateCloneRef('' + fnAttr + '')" style="background
|
|
579110
|
-
'<button onclick="renameCloneRef('' + fnAttr + ''
|
|
579111
|
-
'<button onclick="deleteCloneRef('' + fnAttr + '')" style="background
|
|
580242
|
+
(ref.isActive ? '' : '<button onclick="activateCloneRef(\\'' + fnAttr + '\\')" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-brand);padding:4px 10px;border-radius:3px;font-family:inherit;font-size:0.6rem;cursor:pointer">activate</button>') +
|
|
580243
|
+
'<button onclick="renameCloneRef(\\'' + fnAttr + '\\',\\'' + nameAttr + '\\')" style="background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg-muted);padding:4px 10px;border-radius:3px;font-family:inherit;font-size:0.6rem;cursor:pointer">rename</button>' +
|
|
580244
|
+
'<button onclick="deleteCloneRef(\\'' + fnAttr + '\\')" style="background:var(--color-bg-input);border:1px solid var(--color-error);color:var(--color-error);padding:4px 10px;border-radius:3px;font-family:inherit;font-size:0.6rem;cursor:pointer">del</button>' +
|
|
579112
580245
|
'</div>' +
|
|
579113
580246
|
'</div>';
|
|
579114
580247
|
}).join('');
|
|
579115
580248
|
} catch (e) {
|
|
579116
|
-
list.innerHTML = '<div style="color
|
|
580249
|
+
list.innerHTML = '<div style="color:var(--color-error)">failed: ' + (e?.message || e) + '</div>';
|
|
579117
580250
|
}
|
|
579118
580251
|
}
|
|
579119
580252
|
|
|
@@ -579125,7 +580258,7 @@ async function uploadCloneRef(files) {
|
|
|
579125
580258
|
if (file.size < 4096) { alert('Audio file too small (min ~4 KB)'); return; }
|
|
579126
580259
|
if (file.size > 50 * 1024 * 1024) { alert('Audio file too large (max 50 MB)'); return; }
|
|
579127
580260
|
const list = document.getElementById('clone-refs-list');
|
|
579128
|
-
if (list) list.innerHTML = '<div style="color
|
|
580261
|
+
if (list) list.innerHTML = '<div style="color:var(--color-fg-subtle);padding:14px;text-align:center">uploading ' + escHtml(file.name) + '...</div>';
|
|
579129
580262
|
try {
|
|
579130
580263
|
const buf = await file.arrayBuffer();
|
|
579131
580264
|
let binary = '';
|
|
@@ -579221,7 +580354,7 @@ async function startVoiceChat() {
|
|
|
579221
580354
|
}
|
|
579222
580355
|
if (_voiceAudioCtx.state === 'suspended') await _voiceAudioCtx.resume();
|
|
579223
580356
|
$voiceConnected.set(true);
|
|
579224
|
-
if (btn) { btn.textContent = 'stop voicechat'; btn.style.borderColor = '
|
|
580357
|
+
if (btn) { btn.textContent = 'stop voicechat'; btn.style.borderColor = 'var(--color-error)'; btn.style.color = 'var(--color-error)'; btn.disabled = false; }
|
|
579225
580358
|
document.getElementById('voice-mic-pill').style.display = 'inline-block';
|
|
579226
580359
|
document.getElementById('voice-transcript-pane').style.display = 'block';
|
|
579227
580360
|
addVoiceTranscriptLine('system', 'connected — speak naturally');
|
|
@@ -579239,7 +580372,7 @@ async function stopVoiceChat(silent) {
|
|
|
579239
580372
|
try { if (_voiceWs) _voiceWs.close(); _voiceWs = null; } catch {}
|
|
579240
580373
|
$voiceConnected.set(false);
|
|
579241
580374
|
const btn = document.getElementById('voice-toggle-btn');
|
|
579242
|
-
if (btn) { btn.textContent = 'start voicechat'; btn.style.borderColor = '
|
|
580375
|
+
if (btn) { btn.textContent = 'start voicechat'; btn.style.borderColor = 'var(--color-brand)'; btn.style.color = 'var(--color-brand)'; btn.disabled = false; }
|
|
579243
580376
|
const micPill = document.getElementById('voice-mic-pill');
|
|
579244
580377
|
if (micPill) micPill.style.display = 'none';
|
|
579245
580378
|
if (!silent) addVoiceTranscriptLine('system', 'disconnected');
|
|
@@ -579333,12 +580466,12 @@ function addVoiceTranscriptLine(speaker, text) {
|
|
|
579333
580466
|
const pane = document.getElementById('voice-transcript-pane');
|
|
579334
580467
|
if (!pane) return;
|
|
579335
580468
|
const div = document.createElement('div');
|
|
579336
|
-
const accent = speaker === 'agent' ? '
|
|
579337
|
-
: (speaker === 'you' || speaker === 'you (partial)') ? '
|
|
579338
|
-
: speaker === 'error' ? '
|
|
579339
|
-
div.style.cssText = 'margin:4px 0;padding:4px 6px;border-left:2px solid ' + accent + ';background
|
|
579340
|
-
div.innerHTML = '<span style="color
|
|
579341
|
-
'<span style="color
|
|
580469
|
+
const accent = speaker === 'agent' ? 'var(--color-brand)'
|
|
580470
|
+
: (speaker === 'you' || speaker === 'you (partial)') ? 'var(--color-success)'
|
|
580471
|
+
: speaker === 'error' ? 'var(--color-error)' : 'var(--color-fg-muted)';
|
|
580472
|
+
div.style.cssText = 'margin:4px 0;padding:4px 6px;border-left:2px solid ' + accent + ';background:var(--color-bg)';
|
|
580473
|
+
div.innerHTML = '<span style="color:var(--color-fg-muted);font-size:0.55rem">' + escHtml(speaker) + '</span> ' +
|
|
580474
|
+
'<span style="color:var(--color-fg)">' + escHtml(text) + '</span>';
|
|
579342
580475
|
pane.appendChild(div);
|
|
579343
580476
|
pane.scrollTop = pane.scrollHeight;
|
|
579344
580477
|
while (pane.children.length > 200) pane.removeChild(pane.firstChild);
|
|
@@ -579415,8 +580548,1063 @@ setInterval(pollMetrics, 10000);
|
|
|
579415
580548
|
setInterval(loadScheduled, 15000);
|
|
579416
580549
|
setInterval(loadServices, 30000);
|
|
579417
580550
|
setInterval(pollVersionBump, 5000);
|
|
580551
|
+
|
|
580552
|
+
// ════════════════════════════════════════════════════════════
|
|
580553
|
+
// OWUI-3: Prism.js syntax highlight loader (CDN with offline-
|
|
580554
|
+
// graceful fallback). When Prism isn't available we silently
|
|
580555
|
+
// fall back to plain text — the served HTML doesn't need it
|
|
580556
|
+
// to be syntactically meaningful.
|
|
580557
|
+
// ════════════════════════════════════════════════════════════
|
|
580558
|
+
let _prismLoadAttempted = false;
|
|
580559
|
+
let _prismReady = false;
|
|
580560
|
+
function _ensurePrismLoaded() {
|
|
580561
|
+
if (_prismLoadAttempted) return;
|
|
580562
|
+
_prismLoadAttempted = true;
|
|
580563
|
+
// Skip on file:// and totally offline contexts.
|
|
580564
|
+
if (location.protocol === 'file:') return;
|
|
580565
|
+
const cssLink = document.createElement('link');
|
|
580566
|
+
cssLink.rel = 'stylesheet';
|
|
580567
|
+
cssLink.href = 'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css';
|
|
580568
|
+
cssLink.onerror = () => { /* ignore — fallback is plain code */ };
|
|
580569
|
+
document.head.appendChild(cssLink);
|
|
580570
|
+
const script = document.createElement('script');
|
|
580571
|
+
script.src = 'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js';
|
|
580572
|
+
script.onload = () => {
|
|
580573
|
+
const auto = document.createElement('script');
|
|
580574
|
+
auto.src = 'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js';
|
|
580575
|
+
auto.onload = () => {
|
|
580576
|
+
try {
|
|
580577
|
+
if (window.Prism && window.Prism.plugins && window.Prism.plugins.autoloader) {
|
|
580578
|
+
window.Prism.plugins.autoloader.languages_path = 'https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/';
|
|
580579
|
+
}
|
|
580580
|
+
} catch {}
|
|
580581
|
+
_prismReady = true;
|
|
580582
|
+
// Highlight any code blocks that already exist.
|
|
580583
|
+
try { _highlightCodeBlocks(document); } catch {}
|
|
580584
|
+
};
|
|
580585
|
+
auto.onerror = () => { /* fallback: leave code unhighlighted */ };
|
|
580586
|
+
document.head.appendChild(auto);
|
|
580587
|
+
};
|
|
580588
|
+
script.onerror = () => { /* fallback: leave code unhighlighted */ };
|
|
580589
|
+
document.head.appendChild(script);
|
|
580590
|
+
}
|
|
580591
|
+
|
|
580592
|
+
function _highlightCodeBlocks(root) {
|
|
580593
|
+
if (!root) return;
|
|
580594
|
+
if (!_prismReady) {
|
|
580595
|
+
// Defer until prism finishes loading.
|
|
580596
|
+
if (!_prismLoadAttempted) _ensurePrismLoaded();
|
|
580597
|
+
return;
|
|
580598
|
+
}
|
|
580599
|
+
const codes = root.querySelectorAll ? root.querySelectorAll('pre > code[data-lang]') : [];
|
|
580600
|
+
codes.forEach(c => {
|
|
580601
|
+
if (c.dataset.prismHighlighted === '1') return;
|
|
580602
|
+
const lang = (c.getAttribute('data-lang') || '').toLowerCase();
|
|
580603
|
+
if (!lang) return;
|
|
580604
|
+
c.classList.add('language-' + lang);
|
|
580605
|
+
try {
|
|
580606
|
+
window.Prism && window.Prism.highlightElement && window.Prism.highlightElement(c);
|
|
580607
|
+
c.dataset.prismHighlighted = '1';
|
|
580608
|
+
} catch {}
|
|
580609
|
+
});
|
|
580610
|
+
}
|
|
580611
|
+
|
|
580612
|
+
// Kick the Prism loader once on init — no-op if it's already running.
|
|
580613
|
+
_ensurePrismLoaded();
|
|
580614
|
+
|
|
580615
|
+
// ════════════════════════════════════════════════════════════
|
|
580616
|
+
// OWUI-3: Streaming indicator — pulsing dot + status label.
|
|
580617
|
+
// Owned by send()/agent flows. Mounts inside the active
|
|
580618
|
+
// assistant .msg div; auto-removed when the next chunk lands
|
|
580619
|
+
// or when streaming flips to false.
|
|
580620
|
+
// ════════════════════════════════════════════════════════════
|
|
580621
|
+
function showStreamingIndicator(msgDiv, label) {
|
|
580622
|
+
if (!msgDiv) return null;
|
|
580623
|
+
let ind = msgDiv.querySelector('.msg-streaming-indicator');
|
|
580624
|
+
if (!ind) {
|
|
580625
|
+
ind = document.createElement('div');
|
|
580626
|
+
ind.className = 'msg-streaming-indicator';
|
|
580627
|
+
ind.innerHTML = '<span class="dot"></span><span class="label"></span>';
|
|
580628
|
+
msgDiv.appendChild(ind);
|
|
580629
|
+
}
|
|
580630
|
+
ind.querySelector('.label').textContent = label || 'thinking';
|
|
580631
|
+
return ind;
|
|
580632
|
+
}
|
|
580633
|
+
function hideStreamingIndicator(msgDiv) {
|
|
580634
|
+
if (!msgDiv) return;
|
|
580635
|
+
const ind = msgDiv.querySelector('.msg-streaming-indicator');
|
|
580636
|
+
if (ind) ind.remove();
|
|
580637
|
+
}
|
|
580638
|
+
|
|
580639
|
+
// ════════════════════════════════════════════════════════════
|
|
580640
|
+
// OWUI-2: Sidebar wiring — toggle, resize, status sync, recent
|
|
580641
|
+
// chat list. State persists in localStorage (per-project scoped
|
|
580642
|
+
// via the existing project-namespaced storage helpers).
|
|
580643
|
+
// ════════════════════════════════════════════════════════════
|
|
580644
|
+
const SIDEBAR_KEYS = { collapsed: 'oa.sidebar.collapsed', width: 'oa.sidebar.width' };
|
|
580645
|
+
|
|
580646
|
+
function _getLS(k, fallback) {
|
|
580647
|
+
try { const v = (typeof projLS === 'function' ? projLS() : localStorage).getItem(k); return v == null ? fallback : v; }
|
|
580648
|
+
catch { return fallback; }
|
|
580649
|
+
}
|
|
580650
|
+
function _setLS(k, v) {
|
|
580651
|
+
try { (typeof projLS === 'function' ? projLS() : localStorage).setItem(k, v); } catch {}
|
|
580652
|
+
}
|
|
580653
|
+
|
|
580654
|
+
function toggleSidebar() {
|
|
580655
|
+
const sb = document.getElementById('oa-sidebar');
|
|
580656
|
+
if (!sb) return;
|
|
580657
|
+
const next = sb.getAttribute('data-collapsed') !== 'true';
|
|
580658
|
+
sb.setAttribute('data-collapsed', String(next));
|
|
580659
|
+
_setLS(SIDEBAR_KEYS.collapsed, next ? '1' : '0');
|
|
580660
|
+
}
|
|
580661
|
+
|
|
580662
|
+
function _restoreSidebarState() {
|
|
580663
|
+
const sb = document.getElementById('oa-sidebar');
|
|
580664
|
+
if (!sb) return;
|
|
580665
|
+
const collapsed = _getLS(SIDEBAR_KEYS.collapsed, '0') === '1';
|
|
580666
|
+
sb.setAttribute('data-collapsed', String(collapsed));
|
|
580667
|
+
const w = parseInt(_getLS(SIDEBAR_KEYS.width, '260'), 10);
|
|
580668
|
+
if (!collapsed && Number.isFinite(w) && w >= 200 && w <= 480) {
|
|
580669
|
+
sb.style.width = w + 'px';
|
|
580670
|
+
}
|
|
580671
|
+
}
|
|
580672
|
+
|
|
580673
|
+
function _wireSidebarResize() {
|
|
580674
|
+
const handle = document.getElementById('sidebar-resize');
|
|
580675
|
+
const sb = document.getElementById('oa-sidebar');
|
|
580676
|
+
if (!handle || !sb) return;
|
|
580677
|
+
let dragging = false;
|
|
580678
|
+
let startX = 0, startW = 0;
|
|
580679
|
+
handle.addEventListener('mousedown', (e) => {
|
|
580680
|
+
if (sb.getAttribute('data-collapsed') === 'true') return;
|
|
580681
|
+
dragging = true;
|
|
580682
|
+
startX = e.clientX;
|
|
580683
|
+
startW = sb.getBoundingClientRect().width;
|
|
580684
|
+
handle.classList.add('dragging');
|
|
580685
|
+
document.body.style.userSelect = 'none';
|
|
580686
|
+
e.preventDefault();
|
|
580687
|
+
});
|
|
580688
|
+
window.addEventListener('mousemove', (e) => {
|
|
580689
|
+
if (!dragging) return;
|
|
580690
|
+
const next = Math.max(200, Math.min(480, startW + (e.clientX - startX)));
|
|
580691
|
+
sb.style.width = next + 'px';
|
|
580692
|
+
});
|
|
580693
|
+
window.addEventListener('mouseup', () => {
|
|
580694
|
+
if (!dragging) return;
|
|
580695
|
+
dragging = false;
|
|
580696
|
+
handle.classList.remove('dragging');
|
|
580697
|
+
document.body.style.userSelect = '';
|
|
580698
|
+
const w = Math.round(sb.getBoundingClientRect().width);
|
|
580699
|
+
_setLS(SIDEBAR_KEYS.width, String(w));
|
|
580700
|
+
});
|
|
580701
|
+
}
|
|
580702
|
+
|
|
580703
|
+
// Mirror the legacy #status into the sidebar footer (text + dot color).
|
|
580704
|
+
function _syncSidebarStatus() {
|
|
580705
|
+
const src = document.getElementById('status');
|
|
580706
|
+
const dst = document.getElementById('sidebar-status-text');
|
|
580707
|
+
const dot = document.getElementById('sidebar-status-dot');
|
|
580708
|
+
if (!src || !dst) return;
|
|
580709
|
+
const text = (src.textContent || '').trim();
|
|
580710
|
+
dst.textContent = text || 'idle';
|
|
580711
|
+
if (dot) {
|
|
580712
|
+
let color = 'var(--color-fg-faint)';
|
|
580713
|
+
if (/connect/i.test(text)) color = 'var(--color-warning)';
|
|
580714
|
+
else if (/online|ready|ok/i.test(text)) color = 'var(--color-success)';
|
|
580715
|
+
else if (/error|fail|down/i.test(text)) color = 'var(--color-error)';
|
|
580716
|
+
else if (text) color = 'var(--color-success)';
|
|
580717
|
+
dot.style.background = color;
|
|
580718
|
+
}
|
|
580719
|
+
// Mirror the legacy update-btn visibility into the sidebar.
|
|
580720
|
+
const upd = document.getElementById('update-btn');
|
|
580721
|
+
const sUpd = document.getElementById('sidebar-update-btn');
|
|
580722
|
+
if (upd && sUpd) sUpd.style.display = upd.style.display === 'none' ? 'none' : 'inline-block';
|
|
580723
|
+
}
|
|
580724
|
+
|
|
580725
|
+
// Populate the sidebar recent-chat list from $chatSessions.
|
|
580726
|
+
// ════════════════════════════════════════════════════════════
|
|
580727
|
+
// OWUI-6: Folders + pinned + inline rename/delete
|
|
580728
|
+
// State lives in per-project localStorage:
|
|
580729
|
+
// oa.pinned -> [chatId, ...]
|
|
580730
|
+
// oa.folders -> [{ id, name }]
|
|
580731
|
+
// oa.chatFolderMap -> { chatId: folderId }
|
|
580732
|
+
// oa.foldersOpen -> { folderId: bool }
|
|
580733
|
+
// ════════════════════════════════════════════════════════════
|
|
580734
|
+
const CHAT_KEYS = {
|
|
580735
|
+
pinned: 'oa.pinned',
|
|
580736
|
+
folders: 'oa.folders',
|
|
580737
|
+
map: 'oa.chatFolderMap',
|
|
580738
|
+
foldersOpen: 'oa.foldersOpen',
|
|
580739
|
+
};
|
|
580740
|
+
|
|
580741
|
+
function _readJSON(key, fallback) {
|
|
580742
|
+
try {
|
|
580743
|
+
const raw = (typeof projLS === 'function' ? projLS() : localStorage).getItem(key);
|
|
580744
|
+
if (!raw) return fallback;
|
|
580745
|
+
return JSON.parse(raw);
|
|
580746
|
+
} catch { return fallback; }
|
|
580747
|
+
}
|
|
580748
|
+
function _writeJSON(key, val) {
|
|
580749
|
+
try { (typeof projLS === 'function' ? projLS() : localStorage).setItem(key, JSON.stringify(val)); } catch {}
|
|
580750
|
+
}
|
|
580751
|
+
|
|
580752
|
+
function getChatPinned() { return _readJSON(CHAT_KEYS.pinned, []); }
|
|
580753
|
+
function getChatFolders() { return _readJSON(CHAT_KEYS.folders, []); }
|
|
580754
|
+
function getChatFolderMap() { return _readJSON(CHAT_KEYS.map, {}); }
|
|
580755
|
+
function getFoldersOpenState() { return _readJSON(CHAT_KEYS.foldersOpen, {}); }
|
|
580756
|
+
|
|
580757
|
+
function setChatPinned(arr) { _writeJSON(CHAT_KEYS.pinned, arr || []); }
|
|
580758
|
+
function setChatFolders(arr) { _writeJSON(CHAT_KEYS.folders, arr || []); }
|
|
580759
|
+
function setChatFolderMap(map) { _writeJSON(CHAT_KEYS.map, map || {}); }
|
|
580760
|
+
function setFoldersOpenState(s) { _writeJSON(CHAT_KEYS.foldersOpen, s || {}); }
|
|
580761
|
+
|
|
580762
|
+
function pinChat(id) {
|
|
580763
|
+
const p = getChatPinned();
|
|
580764
|
+
if (!p.includes(id)) { p.push(id); setChatPinned(p); }
|
|
580765
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580766
|
+
}
|
|
580767
|
+
function unpinChat(id) {
|
|
580768
|
+
const p = getChatPinned().filter(x => x !== id);
|
|
580769
|
+
setChatPinned(p);
|
|
580770
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580771
|
+
}
|
|
580772
|
+
function isPinned(id) { return getChatPinned().includes(id); }
|
|
580773
|
+
|
|
580774
|
+
function createChatFolder(name) {
|
|
580775
|
+
const list = getChatFolders();
|
|
580776
|
+
const id = 'fld-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 6);
|
|
580777
|
+
list.push({ id, name: String(name || 'New folder') });
|
|
580778
|
+
setChatFolders(list);
|
|
580779
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580780
|
+
return id;
|
|
580781
|
+
}
|
|
580782
|
+
function renameChatFolder(folderId, newName) {
|
|
580783
|
+
const list = getChatFolders().map(f => f.id === folderId ? { ...f, name: String(newName || f.name) } : f);
|
|
580784
|
+
setChatFolders(list);
|
|
580785
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580786
|
+
}
|
|
580787
|
+
function deleteChatFolder(folderId) {
|
|
580788
|
+
// Move every chat out of this folder, then remove the folder.
|
|
580789
|
+
const map = getChatFolderMap();
|
|
580790
|
+
for (const k of Object.keys(map)) {
|
|
580791
|
+
if (map[k] === folderId) delete map[k];
|
|
580792
|
+
}
|
|
580793
|
+
setChatFolderMap(map);
|
|
580794
|
+
setChatFolders(getChatFolders().filter(f => f.id !== folderId));
|
|
580795
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580796
|
+
}
|
|
580797
|
+
function moveChatToFolder(chatId, folderId) {
|
|
580798
|
+
const map = getChatFolderMap();
|
|
580799
|
+
if (folderId == null || folderId === '__none__') delete map[chatId];
|
|
580800
|
+
else map[chatId] = folderId;
|
|
580801
|
+
setChatFolderMap(map);
|
|
580802
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580803
|
+
}
|
|
580804
|
+
function toggleChatFolder(folderId) {
|
|
580805
|
+
const open = getFoldersOpenState();
|
|
580806
|
+
open[folderId] = !open[folderId];
|
|
580807
|
+
setFoldersOpenState(open);
|
|
580808
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580809
|
+
}
|
|
580810
|
+
|
|
580811
|
+
// Inline rename / delete — best-effort: updates the in-memory store and
|
|
580812
|
+
// localStorage. The server-side session store is touched too via the
|
|
580813
|
+
// existing deleteChatSession() / saveSessions().
|
|
580814
|
+
function renameChatTitle(chatId, newTitle) {
|
|
580815
|
+
if (typeof $chatSessions === 'undefined' || !$chatSessions.set || !$chatSessions.get) return;
|
|
580816
|
+
const all = { ...$chatSessions.get() };
|
|
580817
|
+
const s = all[chatId];
|
|
580818
|
+
if (!s) return;
|
|
580819
|
+
all[chatId] = { ...s, title: String(newTitle || s.title || '') };
|
|
580820
|
+
$chatSessions.set(all);
|
|
580821
|
+
try { if (typeof saveSessions === 'function') saveSessions(); } catch {}
|
|
580822
|
+
}
|
|
580823
|
+
function deleteChatById(chatId) {
|
|
580824
|
+
// Drop from the active session if it matches; otherwise just remove
|
|
580825
|
+
// from the store and let the saveSessions hook persist.
|
|
580826
|
+
if (typeof $chatSessions === 'undefined' || !$chatSessions.set || !$chatSessions.get) return;
|
|
580827
|
+
const all = { ...$chatSessions.get() };
|
|
580828
|
+
delete all[chatId];
|
|
580829
|
+
$chatSessions.set(all);
|
|
580830
|
+
// Also drop from pinned + folder map.
|
|
580831
|
+
setChatPinned(getChatPinned().filter(x => x !== chatId));
|
|
580832
|
+
const map = getChatFolderMap(); delete map[chatId]; setChatFolderMap(map);
|
|
580833
|
+
try { if (typeof saveSessions === 'function') saveSessions(); } catch {}
|
|
580834
|
+
// If we just deleted the active chat, route the user to a fresh new chat.
|
|
580835
|
+
try {
|
|
580836
|
+
const active = (typeof $currentSession !== 'undefined' && $currentSession.get) ? $currentSession.get() : null;
|
|
580837
|
+
if (active === chatId && typeof newChatSession === 'function') newChatSession();
|
|
580838
|
+
} catch {}
|
|
580839
|
+
}
|
|
580840
|
+
|
|
580841
|
+
// Context-menu popover for chat rows
|
|
580842
|
+
function _showChatRowMenu(evt, chatId) {
|
|
580843
|
+
evt.stopPropagation();
|
|
580844
|
+
evt.preventDefault();
|
|
580845
|
+
_hideChatRowMenu();
|
|
580846
|
+
const menu = document.createElement('div');
|
|
580847
|
+
menu.id = 'sb-chat-menu';
|
|
580848
|
+
menu.style.cssText = 'position:fixed;background:var(--color-bg-elevated);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:0 4px 16px rgba(0,0,0,0.4);min-width:160px;z-index:120;font-family:var(--font-ui);font-size:0.78rem;overflow:hidden';
|
|
580849
|
+
const folders = getChatFolders();
|
|
580850
|
+
const safeChat = String(chatId).replace(/'/g, "\\\\'");
|
|
580851
|
+
const pinLabel = isPinned(chatId) ? 'Unpin' : 'Pin';
|
|
580852
|
+
const pinFn = isPinned(chatId) ? 'unpinChat' : 'pinChat';
|
|
580853
|
+
let html = '';
|
|
580854
|
+
html += '<button class="sb-menu-item" onclick="_chatMenuRename(\\'' + safeChat + '\\')">Rename</button>';
|
|
580855
|
+
html += '<button class="sb-menu-item" onclick="' + pinFn + '(\\'' + safeChat + '\\'); _hideChatRowMenu()">' + pinLabel + '</button>';
|
|
580856
|
+
if (folders.length === 0) {
|
|
580857
|
+
html += '<button class="sb-menu-item" onclick="_chatMenuNewFolder(\\'' + safeChat + '\\')">Move to new folder</button>';
|
|
580858
|
+
} else {
|
|
580859
|
+
html += '<div class="sb-menu-sub">Move to folder</div>';
|
|
580860
|
+
html += '<button class="sb-menu-item sub" onclick="moveChatToFolder(\\'' + safeChat + '\\', null); _hideChatRowMenu()">(none)</button>';
|
|
580861
|
+
for (const f of folders) {
|
|
580862
|
+
const safeF = String(f.id).replace(/'/g, "\\\\'");
|
|
580863
|
+
const safeName = String(f.name).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
580864
|
+
html += '<button class="sb-menu-item sub" onclick="moveChatToFolder(\\'' + safeChat + '\\', \\'' + safeF + '\\'); _hideChatRowMenu()">' + safeName + '</button>';
|
|
580865
|
+
}
|
|
580866
|
+
html += '<button class="sb-menu-item sub" onclick="_chatMenuNewFolder(\\'' + safeChat + '\\')">+ New folder...</button>';
|
|
580867
|
+
}
|
|
580868
|
+
html += '<button class="sb-menu-item danger" onclick="_chatMenuDelete(\\'' + safeChat + '\\')">Delete</button>';
|
|
580869
|
+
menu.innerHTML = html;
|
|
580870
|
+
document.body.appendChild(menu);
|
|
580871
|
+
// Position near the click
|
|
580872
|
+
const x = Math.min(evt.clientX, window.innerWidth - 200);
|
|
580873
|
+
const y = Math.min(evt.clientY, window.innerHeight - 220);
|
|
580874
|
+
menu.style.left = x + 'px';
|
|
580875
|
+
menu.style.top = y + 'px';
|
|
580876
|
+
setTimeout(() => document.addEventListener('click', _hideChatRowMenu, { once: true }), 0);
|
|
580877
|
+
}
|
|
580878
|
+
function _hideChatRowMenu() {
|
|
580879
|
+
const m = document.getElementById('sb-chat-menu');
|
|
580880
|
+
if (m) m.remove();
|
|
580881
|
+
}
|
|
580882
|
+
function _chatMenuRename(chatId) {
|
|
580883
|
+
_hideChatRowMenu();
|
|
580884
|
+
const sessions = (typeof $chatSessions !== 'undefined' && $chatSessions.get) ? $chatSessions.get() : {};
|
|
580885
|
+
const cur = (sessions[chatId] && (sessions[chatId].title || sessions[chatId].name)) || '';
|
|
580886
|
+
const next = prompt('Rename chat to:', cur || '');
|
|
580887
|
+
if (next === null) return;
|
|
580888
|
+
renameChatTitle(chatId, next);
|
|
580889
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580890
|
+
}
|
|
580891
|
+
function _chatMenuDelete(chatId) {
|
|
580892
|
+
_hideChatRowMenu();
|
|
580893
|
+
if (!confirm('Delete this chat?')) return;
|
|
580894
|
+
deleteChatById(chatId);
|
|
580895
|
+
_renderSidebarChats(_lastSidebarFilter || '');
|
|
580896
|
+
}
|
|
580897
|
+
function _chatMenuNewFolder(chatId) {
|
|
580898
|
+
_hideChatRowMenu();
|
|
580899
|
+
const name = prompt('New folder name:', 'Folder');
|
|
580900
|
+
if (!name) return;
|
|
580901
|
+
const id = createChatFolder(name);
|
|
580902
|
+
if (chatId) moveChatToFolder(chatId, id);
|
|
580903
|
+
}
|
|
580904
|
+
|
|
580905
|
+
// Folder row context menu
|
|
580906
|
+
function _showFolderRowMenu(evt, folderId) {
|
|
580907
|
+
evt.stopPropagation();
|
|
580908
|
+
evt.preventDefault();
|
|
580909
|
+
_hideChatRowMenu();
|
|
580910
|
+
const menu = document.createElement('div');
|
|
580911
|
+
menu.id = 'sb-chat-menu';
|
|
580912
|
+
menu.style.cssText = 'position:fixed;background:var(--color-bg-elevated);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:0 4px 16px rgba(0,0,0,0.4);min-width:160px;z-index:120;font-family:var(--font-ui);font-size:0.78rem;overflow:hidden';
|
|
580913
|
+
const safeF = String(folderId).replace(/'/g, "\\\\'");
|
|
580914
|
+
menu.innerHTML =
|
|
580915
|
+
'<button class="sb-menu-item" onclick="_folderMenuRename(\\'' + safeF + '\\')">Rename folder</button>' +
|
|
580916
|
+
'<button class="sb-menu-item danger" onclick="_folderMenuDelete(\\'' + safeF + '\\')">Delete folder</button>';
|
|
580917
|
+
document.body.appendChild(menu);
|
|
580918
|
+
const x = Math.min(evt.clientX, window.innerWidth - 200);
|
|
580919
|
+
const y = Math.min(evt.clientY, window.innerHeight - 100);
|
|
580920
|
+
menu.style.left = x + 'px';
|
|
580921
|
+
menu.style.top = y + 'px';
|
|
580922
|
+
setTimeout(() => document.addEventListener('click', _hideChatRowMenu, { once: true }), 0);
|
|
580923
|
+
}
|
|
580924
|
+
function _folderMenuRename(folderId) {
|
|
580925
|
+
_hideChatRowMenu();
|
|
580926
|
+
const f = getChatFolders().find(x => x.id === folderId);
|
|
580927
|
+
if (!f) return;
|
|
580928
|
+
const next = prompt('Rename folder to:', f.name);
|
|
580929
|
+
if (next == null) return;
|
|
580930
|
+
renameChatFolder(folderId, next);
|
|
580931
|
+
}
|
|
580932
|
+
function _folderMenuDelete(folderId) {
|
|
580933
|
+
_hideChatRowMenu();
|
|
580934
|
+
if (!confirm('Delete folder? Chats inside will move to the unsorted list.')) return;
|
|
580935
|
+
deleteChatFolder(folderId);
|
|
580936
|
+
}
|
|
580937
|
+
|
|
580938
|
+
// Drag handlers — chat rows are draggable, folder headers are drop targets.
|
|
580939
|
+
function _onChatDragStart(ev, chatId) {
|
|
580940
|
+
ev.dataTransfer.setData('text/plain', 'oa-chat:' + chatId);
|
|
580941
|
+
ev.dataTransfer.effectAllowed = 'move';
|
|
580942
|
+
}
|
|
580943
|
+
function _onFolderDragOver(ev) {
|
|
580944
|
+
ev.preventDefault();
|
|
580945
|
+
ev.dataTransfer.dropEffect = 'move';
|
|
580946
|
+
ev.currentTarget.classList.add('drop-target');
|
|
580947
|
+
}
|
|
580948
|
+
function _onFolderDragLeave(ev) {
|
|
580949
|
+
ev.currentTarget.classList.remove('drop-target');
|
|
580950
|
+
}
|
|
580951
|
+
function _onFolderDrop(ev, folderId) {
|
|
580952
|
+
ev.preventDefault();
|
|
580953
|
+
ev.currentTarget.classList.remove('drop-target');
|
|
580954
|
+
const data = ev.dataTransfer.getData('text/plain') || '';
|
|
580955
|
+
if (!data.startsWith('oa-chat:')) return;
|
|
580956
|
+
const chatId = data.slice('oa-chat:'.length);
|
|
580957
|
+
moveChatToFolder(chatId, folderId);
|
|
580958
|
+
}
|
|
580959
|
+
|
|
580960
|
+
// Build the new-folder UI button (lives at the top of the chats nav)
|
|
580961
|
+
function _renderSidebarFoldersToolbar() {
|
|
580962
|
+
const host = document.getElementById('sidebar-folder-tools');
|
|
580963
|
+
if (!host) return;
|
|
580964
|
+
host.innerHTML =
|
|
580965
|
+
'<button class="sb-folder-action" onclick="(function(){const n=prompt(\\'New folder name:\\',\\'Folder\\');if(n)createChatFolder(n);})()" title="New folder">+ folder</button>';
|
|
580966
|
+
}
|
|
580967
|
+
|
|
580968
|
+
let _lastSidebarFilter = '';
|
|
580969
|
+
|
|
580970
|
+
function _renderSidebarChats(filter) {
|
|
580971
|
+
const list = document.getElementById('sidebar-recent-list');
|
|
580972
|
+
if (!list) return;
|
|
580973
|
+
_lastSidebarFilter = filter || '';
|
|
580974
|
+
_renderSidebarFoldersToolbar();
|
|
580975
|
+
|
|
580976
|
+
const sessions = (typeof $chatSessions !== 'undefined' && $chatSessions.get) ? $chatSessions.get() : {};
|
|
580977
|
+
const allIds = Object.keys(sessions || {});
|
|
580978
|
+
const q = (filter || '').trim().toLowerCase();
|
|
580979
|
+
const activeId = (typeof $currentSession !== 'undefined' && $currentSession.get) ? $currentSession.get() : null;
|
|
580980
|
+
|
|
580981
|
+
const folders = getChatFolders();
|
|
580982
|
+
const pinned = getChatPinned();
|
|
580983
|
+
const folderMap = getChatFolderMap();
|
|
580984
|
+
const folderOpen = getFoldersOpenState();
|
|
580985
|
+
|
|
580986
|
+
// Sort helper
|
|
580987
|
+
const sortByRecency = (a, b) => {
|
|
580988
|
+
const sa = sessions[a] || {}, sb = sessions[b] || {};
|
|
580989
|
+
const ta = sa.updated_at || sa.created_at || a;
|
|
580990
|
+
const tb = sb.updated_at || sb.created_at || b;
|
|
580991
|
+
return String(tb).localeCompare(String(ta));
|
|
580992
|
+
};
|
|
580993
|
+
allIds.sort(sortByRecency);
|
|
580994
|
+
|
|
580995
|
+
// Filter helper — match title OR last message snippet (best effort).
|
|
580996
|
+
const matches = (id) => {
|
|
580997
|
+
if (!q) return true;
|
|
580998
|
+
const s = sessions[id] || {};
|
|
580999
|
+
const title = String(s.title || s.name || '');
|
|
581000
|
+
if (title.toLowerCase().includes(q)) return true;
|
|
581001
|
+
const last = (Array.isArray(s.messages) && s.messages.length)
|
|
581002
|
+
? String(s.messages[s.messages.length - 1].content || '')
|
|
581003
|
+
: '';
|
|
581004
|
+
return last.toLowerCase().includes(q);
|
|
581005
|
+
};
|
|
581006
|
+
|
|
581007
|
+
// Build rendering buckets
|
|
581008
|
+
const inFolder = {};
|
|
581009
|
+
for (const f of folders) inFolder[f.id] = [];
|
|
581010
|
+
const unsorted = [];
|
|
581011
|
+
for (const id of allIds) {
|
|
581012
|
+
if (!matches(id)) continue;
|
|
581013
|
+
const fId = folderMap[id];
|
|
581014
|
+
if (fId && inFolder[fId]) inFolder[fId].push(id);
|
|
581015
|
+
else unsorted.push(id);
|
|
581016
|
+
}
|
|
581017
|
+
|
|
581018
|
+
const renderRow = (id) => {
|
|
581019
|
+
const s = sessions[id] || {};
|
|
581020
|
+
const title = (s.title || s.name || ('Chat ' + String(id).slice(0, 8))).toString();
|
|
581021
|
+
const cls = 'sb-chat' + (id === activeId ? ' active' : '');
|
|
581022
|
+
const safeId = String(id).replace(/'/g, "\\\\'");
|
|
581023
|
+
const safeTitle = title.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
581024
|
+
return '<div class="' + cls + '" draggable="true" ondragstart="_onChatDragStart(event,\\'' + safeId + '\\')" onclick="switchTab(\\'chat\\'); switchSession(\\'' + safeId + '\\')" title="' + safeTitle + '">' +
|
|
581025
|
+
'<span class="sb-chat-title">' + safeTitle + '</span>' +
|
|
581026
|
+
'<button class="sb-chat-menu" onclick="_showChatRowMenu(event,\\'' + safeId + '\\')" title="More">⋮</button>' +
|
|
581027
|
+
'</div>';
|
|
581028
|
+
};
|
|
581029
|
+
|
|
581030
|
+
// Pinned section
|
|
581031
|
+
const pinnedHost = document.getElementById('sidebar-pinned-list');
|
|
581032
|
+
const pinnedSect = document.getElementById('sidebar-pinned-section');
|
|
581033
|
+
if (pinnedHost && pinnedSect) {
|
|
581034
|
+
const pinnedFiltered = pinned.filter(id => allIds.includes(id) && matches(id));
|
|
581035
|
+
if (pinnedFiltered.length === 0) {
|
|
581036
|
+
pinnedSect.style.display = 'none';
|
|
581037
|
+
pinnedHost.innerHTML = '';
|
|
581038
|
+
} else {
|
|
581039
|
+
pinnedSect.style.display = 'block';
|
|
581040
|
+
pinnedHost.innerHTML = pinnedFiltered.map(renderRow).join('');
|
|
581041
|
+
}
|
|
581042
|
+
}
|
|
581043
|
+
|
|
581044
|
+
// Build folders + unsorted
|
|
581045
|
+
const items = [];
|
|
581046
|
+
for (const f of folders) {
|
|
581047
|
+
const ids = inFolder[f.id] || [];
|
|
581048
|
+
if (q && ids.length === 0) continue; // hide empty folders during a search
|
|
581049
|
+
const open = folderOpen[f.id] !== false; // default open
|
|
581050
|
+
const safeF = String(f.id).replace(/'/g, "\\\\'");
|
|
581051
|
+
const safeName = String(f.name).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
581052
|
+
items.push(
|
|
581053
|
+
'<div class="sb-folder" data-folder="' + safeF + '" ondragover="_onFolderDragOver(event)" ondragleave="_onFolderDragLeave(event)" ondrop="_onFolderDrop(event,\\'' + safeF + '\\')">' +
|
|
581054
|
+
'<div class="sb-folder-row" onclick="toggleChatFolder(\\'' + safeF + '\\')">' +
|
|
581055
|
+
'<span class="sb-folder-caret">' + (open ? '▾' : '▸') + '</span>' +
|
|
581056
|
+
'<span class="sb-folder-name">' + safeName + '</span>' +
|
|
581057
|
+
'<span class="sb-folder-count">' + ids.length + '</span>' +
|
|
581058
|
+
'<button class="sb-folder-menu" onclick="_showFolderRowMenu(event,\\'' + safeF + '\\')" title="More">⋮</button>' +
|
|
581059
|
+
'</div>' +
|
|
581060
|
+
(open ? '<div class="sb-folder-body">' + (ids.length ? ids.map(renderRow).join('') : '<div class="sb-folder-empty">empty</div>') + '</div>' : '') +
|
|
581061
|
+
'</div>'
|
|
581062
|
+
);
|
|
581063
|
+
}
|
|
581064
|
+
for (const id of unsorted) {
|
|
581065
|
+
items.push(renderRow(id));
|
|
581066
|
+
}
|
|
581067
|
+
|
|
581068
|
+
if (items.length === 0) {
|
|
581069
|
+
list.innerHTML = '<div style="padding:14px 8px;color:var(--color-fg-faint);font-size:0.7rem;text-align:center">' + (q ? 'No matches' : 'No chats yet') + '</div>';
|
|
581070
|
+
} else {
|
|
581071
|
+
list.innerHTML = items.join('');
|
|
581072
|
+
}
|
|
581073
|
+
}
|
|
581074
|
+
|
|
581075
|
+
function filterSidebarChats(q) {
|
|
581076
|
+
_renderSidebarChats(q);
|
|
581077
|
+
}
|
|
581078
|
+
|
|
581079
|
+
// Initialize sidebar state + observers.
|
|
581080
|
+
(function _initSidebar() {
|
|
581081
|
+
_restoreSidebarState();
|
|
581082
|
+
_wireSidebarResize();
|
|
581083
|
+
_syncSidebarStatus();
|
|
581084
|
+
_renderSidebarChats('');
|
|
581085
|
+
// Re-render recent list whenever the sessions store changes.
|
|
581086
|
+
if (typeof $chatSessions !== 'undefined' && $chatSessions.subscribe) {
|
|
581087
|
+
$chatSessions.subscribe(() => {
|
|
581088
|
+
const search = document.getElementById('sidebar-search');
|
|
581089
|
+
_renderSidebarChats(search ? search.value : '');
|
|
581090
|
+
});
|
|
581091
|
+
}
|
|
581092
|
+
if (typeof $currentSession !== 'undefined' && $currentSession.subscribe) {
|
|
581093
|
+
$currentSession.subscribe(() => {
|
|
581094
|
+
const search = document.getElementById('sidebar-search');
|
|
581095
|
+
_renderSidebarChats(search ? search.value : '');
|
|
581096
|
+
});
|
|
581097
|
+
}
|
|
581098
|
+
// Poll the legacy status DOM every 2s — cheap and avoids touching every
|
|
581099
|
+
// call site that updates #status.
|
|
581100
|
+
setInterval(_syncSidebarStatus, 2000);
|
|
581101
|
+
// Mark the initial active tab on the sidebar nav.
|
|
581102
|
+
const initial = (typeof $activeTab !== 'undefined' && $activeTab.get) ? $activeTab.get() : 'chat';
|
|
581103
|
+
document.querySelectorAll('#oa-sidebar .sb-nav').forEach(b => {
|
|
581104
|
+
b.classList.toggle('active', b.getAttribute('data-tab') === initial);
|
|
581105
|
+
});
|
|
581106
|
+
// Cmd/Ctrl+B toggles sidebar.
|
|
581107
|
+
document.addEventListener('keydown', (e) => {
|
|
581108
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === 'b' || e.key === 'B')) {
|
|
581109
|
+
e.preventDefault();
|
|
581110
|
+
toggleSidebar();
|
|
581111
|
+
}
|
|
581112
|
+
});
|
|
581113
|
+
})();
|
|
581114
|
+
|
|
581115
|
+
// ════════════════════════════════════════════════════════════
|
|
581116
|
+
// OWUI-5: Settings + Model manager modals
|
|
581117
|
+
// ════════════════════════════════════════════════════════════
|
|
581118
|
+
function openSettingsModal(initialPane) {
|
|
581119
|
+
const m = document.getElementById('settings-modal');
|
|
581120
|
+
if (!m) return;
|
|
581121
|
+
m.style.display = 'flex';
|
|
581122
|
+
document.body.style.overflow = 'hidden';
|
|
581123
|
+
if (initialPane) openSettingsPane(initialPane);
|
|
581124
|
+
}
|
|
581125
|
+
function closeSettingsModal() {
|
|
581126
|
+
const m = document.getElementById('settings-modal');
|
|
581127
|
+
if (!m) return;
|
|
581128
|
+
m.style.display = 'none';
|
|
581129
|
+
document.body.style.overflow = '';
|
|
581130
|
+
}
|
|
581131
|
+
function openSettingsPane(name) {
|
|
581132
|
+
document.querySelectorAll('#settings-modal .rail-tab').forEach(t => {
|
|
581133
|
+
t.classList.toggle('active', t.getAttribute('data-pane') === name);
|
|
581134
|
+
});
|
|
581135
|
+
document.querySelectorAll('#settings-modal .settings-pane').forEach(p => {
|
|
581136
|
+
const isThis = (p.id === 'settings-pane-' + name);
|
|
581137
|
+
p.hidden = !isThis;
|
|
581138
|
+
p.classList.toggle('active', isThis);
|
|
581139
|
+
});
|
|
581140
|
+
// Lazy-load each pane's data on first open.
|
|
581141
|
+
if (name === 'connections') { try { loadSettingsConnections(); } catch {} }
|
|
581142
|
+
if (name === 'models') { try { loadSettingsModels(); } catch {} }
|
|
581143
|
+
if (name === 'voice') { try { loadSettingsVoice(); } catch {} }
|
|
581144
|
+
if (name === 'profiles') { try { loadSettingsProfiles(); } catch {} }
|
|
581145
|
+
}
|
|
581146
|
+
|
|
581147
|
+
// Connections — pulls /v1/config and renders an inline edit form.
|
|
581148
|
+
async function loadSettingsConnections() {
|
|
581149
|
+
const host = document.getElementById('settings-connections-host');
|
|
581150
|
+
if (!host) return;
|
|
581151
|
+
host.innerHTML = '<div style="color:var(--color-fg-muted);font-size:0.74rem">loading...</div>';
|
|
581152
|
+
try {
|
|
581153
|
+
const r = await fetch('/v1/config', { headers: headers() });
|
|
581154
|
+
if (r.status !== 200) {
|
|
581155
|
+
host.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">/v1/config returned ' + r.status + '</div>';
|
|
581156
|
+
return;
|
|
581157
|
+
}
|
|
581158
|
+
const cfg = await r.json();
|
|
581159
|
+
const ep = (cfg && (cfg.endpoint || cfg.backend_url)) || '';
|
|
581160
|
+
const safeEp = String(ep).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
581161
|
+
host.innerHTML =
|
|
581162
|
+
'<label style="display:block;font-size:0.78rem;margin-bottom:6px">Backend endpoint</label>' +
|
|
581163
|
+
'<input id="settings-conn-endpoint" type="text" value="' + safeEp + '" style="width:100%;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:6px 10px;border-radius:var(--radius-sm);font-size:0.78rem">' +
|
|
581164
|
+
'<div style="margin-top:10px"><button class="oa-btn-secondary" onclick="saveSettingsConnections()">save</button></div>' +
|
|
581165
|
+
'<div id="settings-conn-status" style="margin-top:8px;font-size:0.7rem;color:var(--color-fg-muted)"></div>';
|
|
581166
|
+
} catch (e) {
|
|
581167
|
+
host.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">' + e.message + '</div>';
|
|
581168
|
+
}
|
|
581169
|
+
}
|
|
581170
|
+
async function saveSettingsConnections() {
|
|
581171
|
+
const inp = document.getElementById('settings-conn-endpoint');
|
|
581172
|
+
const status = document.getElementById('settings-conn-status');
|
|
581173
|
+
if (!inp || !status) return;
|
|
581174
|
+
status.textContent = 'saving...';
|
|
581175
|
+
try {
|
|
581176
|
+
const r = await fetch('/v1/config/endpoint', {
|
|
581177
|
+
method: 'POST',
|
|
581178
|
+
headers: headers(),
|
|
581179
|
+
body: JSON.stringify({ endpoint: inp.value }),
|
|
581180
|
+
});
|
|
581181
|
+
if (r.status === 200) status.textContent = 'saved';
|
|
581182
|
+
else status.textContent = 'failed (' + r.status + ')';
|
|
581183
|
+
} catch (e) {
|
|
581184
|
+
status.textContent = 'error: ' + e.message;
|
|
581185
|
+
}
|
|
581186
|
+
}
|
|
581187
|
+
|
|
581188
|
+
// Models pane
|
|
581189
|
+
let _settingsModelsCache = [];
|
|
581190
|
+
async function loadSettingsModels() {
|
|
581191
|
+
const host = document.getElementById('settings-models-list');
|
|
581192
|
+
if (!host) return;
|
|
581193
|
+
host.innerHTML = '<div style="padding:8px;color:var(--color-fg-muted);font-size:0.74rem">loading...</div>';
|
|
581194
|
+
try {
|
|
581195
|
+
const r = await fetch('/v1/models', { headers: headers() });
|
|
581196
|
+
if (r.status !== 200) {
|
|
581197
|
+
host.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">/v1/models returned ' + r.status + '</div>';
|
|
581198
|
+
return;
|
|
581199
|
+
}
|
|
581200
|
+
const j = await r.json();
|
|
581201
|
+
const arr = Array.isArray(j) ? j : (Array.isArray(j.data) ? j.data : (Array.isArray(j.models) ? j.models : []));
|
|
581202
|
+
_settingsModelsCache = arr.map(m => ({
|
|
581203
|
+
id: m.id || m.name || String(m),
|
|
581204
|
+
detail: (m.size ? Math.round(Number(m.size) / 1e9 * 10) / 10 + 'GB' : '') + (m.modified_at ? '' : ''),
|
|
581205
|
+
}));
|
|
581206
|
+
filterSettingsModels(document.getElementById('settings-models-search').value || '');
|
|
581207
|
+
} catch (e) {
|
|
581208
|
+
host.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">' + e.message + '</div>';
|
|
581209
|
+
}
|
|
581210
|
+
}
|
|
581211
|
+
function filterSettingsModels(q) {
|
|
581212
|
+
const host = document.getElementById('settings-models-list');
|
|
581213
|
+
if (!host) return;
|
|
581214
|
+
const sel = document.getElementById('model-select');
|
|
581215
|
+
const active = sel ? sel.value : '';
|
|
581216
|
+
const ql = String(q || '').toLowerCase();
|
|
581217
|
+
const rows = _settingsModelsCache
|
|
581218
|
+
.filter(m => !ql || m.id.toLowerCase().includes(ql))
|
|
581219
|
+
.map(m => {
|
|
581220
|
+
const isAct = m.id === active;
|
|
581221
|
+
const safe = m.id.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, ''');
|
|
581222
|
+
return '<div class="row' + (isAct ? ' active' : '') + '" onclick="setActiveModel(\\'' + m.id.replace(/'/g, "\\\\'") + '\\')"><span>' + safe + '</span><span class="right">' + (m.detail || '') + '</span></div>';
|
|
581223
|
+
}).join('');
|
|
581224
|
+
host.innerHTML = rows || '<div style="padding:8px;color:var(--color-fg-muted);font-size:0.74rem">No models</div>';
|
|
581225
|
+
}
|
|
581226
|
+
function setActiveModel(id) {
|
|
581227
|
+
const sel = document.getElementById('model-select');
|
|
581228
|
+
if (!sel) return;
|
|
581229
|
+
sel.value = id;
|
|
581230
|
+
sel.dispatchEvent(new Event('change'));
|
|
581231
|
+
filterSettingsModels(document.getElementById('settings-models-search').value || '');
|
|
581232
|
+
}
|
|
581233
|
+
|
|
581234
|
+
// Voice settings — clone refs CRUD via existing /v1/voice/clones.
|
|
581235
|
+
async function loadSettingsVoice() {
|
|
581236
|
+
const host = document.getElementById('settings-voice-host');
|
|
581237
|
+
if (!host) return;
|
|
581238
|
+
host.innerHTML = '<div style="color:var(--color-fg-muted);font-size:0.74rem">loading...</div>';
|
|
581239
|
+
try {
|
|
581240
|
+
const r = await fetch('/v1/voice/clones', { headers: headers() });
|
|
581241
|
+
if (r.status !== 200) {
|
|
581242
|
+
host.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">/v1/voice/clones returned ' + r.status + '</div>';
|
|
581243
|
+
return;
|
|
581244
|
+
}
|
|
581245
|
+
const j = await r.json();
|
|
581246
|
+
const arr = Array.isArray(j) ? j : (Array.isArray(j.clones) ? j.clones : (Array.isArray(j.refs) ? j.refs : []));
|
|
581247
|
+
if (!arr.length) {
|
|
581248
|
+
host.innerHTML = '<div style="color:var(--color-fg-muted);font-size:0.78rem">No clone refs registered. Use the Voice tab to upload a reference.</div>';
|
|
581249
|
+
return;
|
|
581250
|
+
}
|
|
581251
|
+
host.innerHTML = '<div style="font-size:0.78rem;display:flex;flex-direction:column;gap:6px">' +
|
|
581252
|
+
arr.map(c => {
|
|
581253
|
+
const id = c.id || c.name || '';
|
|
581254
|
+
const lbl = (c.label || c.name || id).toString();
|
|
581255
|
+
const safe = lbl.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
581256
|
+
return '<div class="row" style="display:flex;align-items:center;justify-content:space-between"><span>' + safe + '</span><span class="right">' + (id || '') + '</span></div>';
|
|
581257
|
+
}).join('') +
|
|
581258
|
+
'</div><div style="margin-top:10px"><button class="oa-btn-secondary" onclick="switchTab(\\'voice\\'); closeSettingsModal()">manage in voice tab</button></div>';
|
|
581259
|
+
} catch (e) {
|
|
581260
|
+
host.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">' + e.message + '</div>';
|
|
581261
|
+
}
|
|
581262
|
+
}
|
|
581263
|
+
|
|
581264
|
+
// Profiles pane — defers to existing loadProfiles()
|
|
581265
|
+
async function loadSettingsProfiles() {
|
|
581266
|
+
const host = document.getElementById('settings-profiles-host');
|
|
581267
|
+
if (!host) return;
|
|
581268
|
+
host.innerHTML = '<div style="color:var(--color-fg-muted);font-size:0.74rem">opening agent profiles...</div>';
|
|
581269
|
+
try {
|
|
581270
|
+
if (typeof loadProfiles === 'function') await loadProfiles();
|
|
581271
|
+
host.innerHTML = '<div style="color:var(--color-fg-muted);font-size:0.78rem">Profiles loaded into the Agent tab — switch to Agent to manage them.</div>' +
|
|
581272
|
+
'<div style="margin-top:10px"><button class="oa-btn-secondary" onclick="switchTab(\\'agent\\'); closeSettingsModal()">open agent tab</button></div>';
|
|
581273
|
+
} catch (e) {
|
|
581274
|
+
host.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">' + e.message + '</div>';
|
|
581275
|
+
}
|
|
581276
|
+
}
|
|
581277
|
+
|
|
581278
|
+
// Theme + font scale (stub wiring; OWUI-5 introduces the toggle, light
|
|
581279
|
+
// mode polish is explicitly out of scope per the audit doc).
|
|
581280
|
+
function setOATheme(t) {
|
|
581281
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
581282
|
+
try { localStorage.setItem('oa.theme', t); } catch {}
|
|
581283
|
+
}
|
|
581284
|
+
function setOAFontScale(v) {
|
|
581285
|
+
const n = parseFloat(v);
|
|
581286
|
+
if (!Number.isFinite(n)) return;
|
|
581287
|
+
document.documentElement.style.fontSize = (n * 100) + '%';
|
|
581288
|
+
const disp = document.getElementById('oa-font-scale-display');
|
|
581289
|
+
if (disp) disp.textContent = n.toFixed(2) + 'x';
|
|
581290
|
+
try { localStorage.setItem('oa.font-scale', String(n)); } catch {}
|
|
581291
|
+
}
|
|
581292
|
+
|
|
581293
|
+
// Restore preferences on init
|
|
581294
|
+
(function _restoreOAPrefs() {
|
|
581295
|
+
try {
|
|
581296
|
+
const t = localStorage.getItem('oa.theme'); if (t) document.documentElement.setAttribute('data-theme', t);
|
|
581297
|
+
const f = localStorage.getItem('oa.font-scale'); if (f) document.documentElement.style.fontSize = (parseFloat(f) * 100) + '%';
|
|
581298
|
+
} catch {}
|
|
581299
|
+
})();
|
|
581300
|
+
|
|
581301
|
+
// Model manager modal — separate from the settings/Models pane: bigger
|
|
581302
|
+
// list with a side detail pane. Reuses /v1/models.
|
|
581303
|
+
let _modelManagerCache = [];
|
|
581304
|
+
let _modelManagerSelected = null;
|
|
581305
|
+
function openModelManager() {
|
|
581306
|
+
const m = document.getElementById('model-manager-modal');
|
|
581307
|
+
if (!m) return;
|
|
581308
|
+
m.style.display = 'flex';
|
|
581309
|
+
document.body.style.overflow = 'hidden';
|
|
581310
|
+
loadModelManager();
|
|
581311
|
+
}
|
|
581312
|
+
function closeModelManager() {
|
|
581313
|
+
const m = document.getElementById('model-manager-modal');
|
|
581314
|
+
if (!m) return;
|
|
581315
|
+
m.style.display = 'none';
|
|
581316
|
+
document.body.style.overflow = '';
|
|
581317
|
+
}
|
|
581318
|
+
async function loadModelManager() {
|
|
581319
|
+
const list = document.getElementById('mm-list');
|
|
581320
|
+
if (!list) return;
|
|
581321
|
+
list.innerHTML = '<div style="padding:8px;color:var(--color-fg-muted);font-size:0.74rem">loading...</div>';
|
|
581322
|
+
try {
|
|
581323
|
+
const r = await fetch('/v1/models', { headers: headers() });
|
|
581324
|
+
if (r.status !== 200) {
|
|
581325
|
+
list.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">/v1/models returned ' + r.status + '</div>';
|
|
581326
|
+
return;
|
|
581327
|
+
}
|
|
581328
|
+
const j = await r.json();
|
|
581329
|
+
const arr = Array.isArray(j) ? j : (Array.isArray(j.data) ? j.data : (Array.isArray(j.models) ? j.models : []));
|
|
581330
|
+
_modelManagerCache = arr;
|
|
581331
|
+
filterModelManager(document.getElementById('mm-search').value || '');
|
|
581332
|
+
} catch (e) {
|
|
581333
|
+
list.innerHTML = '<div style="color:var(--color-error);font-size:0.74rem">' + e.message + '</div>';
|
|
581334
|
+
}
|
|
581335
|
+
}
|
|
581336
|
+
function filterModelManager(q) {
|
|
581337
|
+
const list = document.getElementById('mm-list');
|
|
581338
|
+
if (!list) return;
|
|
581339
|
+
const ql = String(q || '').toLowerCase();
|
|
581340
|
+
const rows = _modelManagerCache
|
|
581341
|
+
.filter(m => {
|
|
581342
|
+
const id = m.id || m.name || '';
|
|
581343
|
+
return !ql || String(id).toLowerCase().includes(ql);
|
|
581344
|
+
})
|
|
581345
|
+
.map((m, i) => {
|
|
581346
|
+
const id = m.id || m.name || '';
|
|
581347
|
+
const safe = String(id).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, ''');
|
|
581348
|
+
const sel = (_modelManagerSelected === id) ? ' selected' : '';
|
|
581349
|
+
return '<div class="row' + sel + '" data-id="' + safe + '" onclick="_modelManagerSelect(\\'' + String(id).replace(/'/g, "\\\\'") + '\\')"><span>' + safe + '</span><span class="right">' + (m.size ? Math.round(Number(m.size) / 1e9 * 10) / 10 + 'GB' : '') + '</span></div>';
|
|
581350
|
+
}).join('');
|
|
581351
|
+
list.innerHTML = rows || '<div style="padding:8px;color:var(--color-fg-muted);font-size:0.74rem">No models</div>';
|
|
581352
|
+
}
|
|
581353
|
+
function _modelManagerSelect(id) {
|
|
581354
|
+
_modelManagerSelected = id;
|
|
581355
|
+
filterModelManager(document.getElementById('mm-search').value || '');
|
|
581356
|
+
const detail = document.getElementById('mm-detail');
|
|
581357
|
+
if (!detail) return;
|
|
581358
|
+
const m = _modelManagerCache.find(x => (x.id || x.name) === id) || {};
|
|
581359
|
+
const safe = String(id).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, ''');
|
|
581360
|
+
const rows = [
|
|
581361
|
+
['ID', safe],
|
|
581362
|
+
['Size', m.size ? Math.round(Number(m.size) / 1e9 * 10) / 10 + 'GB' : 'unknown'],
|
|
581363
|
+
['Modified', m.modified_at || m.modifiedAt || '—'],
|
|
581364
|
+
['Family', m.details && m.details.family ? m.details.family : '—'],
|
|
581365
|
+
['Quantization', m.details && m.details.quantization_level ? m.details.quantization_level : '—'],
|
|
581366
|
+
];
|
|
581367
|
+
detail.innerHTML =
|
|
581368
|
+
'<div style="padding:14px;font-size:0.78rem">' +
|
|
581369
|
+
'<h4 style="margin:0 0 10px;color:var(--color-fg)">' + safe + '</h4>' +
|
|
581370
|
+
'<table style="width:100%;border-collapse:collapse">' +
|
|
581371
|
+
rows.map(([k, v]) => '<tr><td style="color:var(--color-fg-muted);padding:3px 6px 3px 0;width:35%">' + k + '</td><td style="color:var(--color-fg);padding:3px 0">' + v + '</td></tr>').join('') +
|
|
581372
|
+
'</table>' +
|
|
581373
|
+
'<div style="margin-top:14px;display:flex;gap:6px;flex-wrap:wrap">' +
|
|
581374
|
+
'<button class="oa-btn-secondary" onclick="setActiveModel(\\'' + String(id).replace(/'/g, "\\\\'") + '\\')">set active</button>' +
|
|
581375
|
+
'<a class="oa-btn-secondary" href="/v1/models" target="_blank" style="text-decoration:none;display:inline-block">raw json</a>' +
|
|
581376
|
+
'</div>' +
|
|
581377
|
+
'</div>';
|
|
581378
|
+
}
|
|
581379
|
+
|
|
581380
|
+
// Global keybindings
|
|
581381
|
+
document.addEventListener('keydown', (e) => {
|
|
581382
|
+
// Escape closes the topmost modal.
|
|
581383
|
+
if (e.key === 'Escape') {
|
|
581384
|
+
const sm = document.getElementById('settings-modal');
|
|
581385
|
+
const mm = document.getElementById('model-manager-modal');
|
|
581386
|
+
if (mm && mm.style.display === 'flex') { closeModelManager(); return; }
|
|
581387
|
+
if (sm && sm.style.display === 'flex') { closeSettingsModal(); return; }
|
|
581388
|
+
}
|
|
581389
|
+
// Cmd/Ctrl+, opens settings.
|
|
581390
|
+
if ((e.metaKey || e.ctrlKey) && e.key === ',') {
|
|
581391
|
+
e.preventDefault();
|
|
581392
|
+
openSettingsModal('general');
|
|
581393
|
+
}
|
|
581394
|
+
});
|
|
581395
|
+
|
|
581396
|
+
// ════════════════════════════════════════════════════════════
|
|
581397
|
+
// OWUI-4: Input toolbar + slash palette + drop-to-attach
|
|
581398
|
+
// ════════════════════════════════════════════════════════════
|
|
581399
|
+
|
|
581400
|
+
let _attachments = []; // { name, size, file }
|
|
581401
|
+
let _slashItems = []; // cached commands list from /v1/commands
|
|
581402
|
+
let _slashSelectedIdx = 0;
|
|
581403
|
+
let _slashLoaded = false;
|
|
581404
|
+
|
|
581405
|
+
function _slashPaletteIsOpen() {
|
|
581406
|
+
const p = document.getElementById('slash-palette');
|
|
581407
|
+
return p && p.style.display !== 'none';
|
|
581408
|
+
}
|
|
581409
|
+
async function _ensureSlashList() {
|
|
581410
|
+
if (_slashLoaded) return _slashItems;
|
|
581411
|
+
try {
|
|
581412
|
+
const r = await fetch('/v1/commands', { headers: headers() });
|
|
581413
|
+
if (r.status === 200) {
|
|
581414
|
+
const j = await r.json();
|
|
581415
|
+
// The endpoint returns either an array of {name, description} or
|
|
581416
|
+
// an envelope { commands: [...] }. Be tolerant.
|
|
581417
|
+
const arr = Array.isArray(j) ? j : (Array.isArray(j.commands) ? j.commands : []);
|
|
581418
|
+
_slashItems = arr.map(c => ({
|
|
581419
|
+
name: (c.name || c.cmd || c.command || '').replace(/^\\//, ''),
|
|
581420
|
+
desc: c.description || c.desc || c.summary || '',
|
|
581421
|
+
})).filter(c => c.name);
|
|
581422
|
+
}
|
|
581423
|
+
} catch {}
|
|
581424
|
+
_slashLoaded = true;
|
|
581425
|
+
return _slashItems;
|
|
581426
|
+
}
|
|
581427
|
+
|
|
581428
|
+
function openSlashPalette() {
|
|
581429
|
+
const v = (input.value || '').trim();
|
|
581430
|
+
if (!v.startsWith('/')) {
|
|
581431
|
+
input.value = '/' + (input.value || '');
|
|
581432
|
+
}
|
|
581433
|
+
input.focus();
|
|
581434
|
+
_maybeUpdateSlashPalette();
|
|
581435
|
+
}
|
|
581436
|
+
|
|
581437
|
+
function closeSlashPalette() {
|
|
581438
|
+
const p = document.getElementById('slash-palette');
|
|
581439
|
+
if (p) p.style.display = 'none';
|
|
581440
|
+
_slashSelectedIdx = 0;
|
|
581441
|
+
}
|
|
581442
|
+
|
|
581443
|
+
async function _maybeUpdateSlashPalette() {
|
|
581444
|
+
const v = input.value || '';
|
|
581445
|
+
const p = document.getElementById('slash-palette');
|
|
581446
|
+
if (!p) return;
|
|
581447
|
+
if (!v.startsWith('/')) {
|
|
581448
|
+
p.style.display = 'none';
|
|
581449
|
+
return;
|
|
581450
|
+
}
|
|
581451
|
+
await _ensureSlashList();
|
|
581452
|
+
const q = v.slice(1).split(/\\s/)[0].toLowerCase();
|
|
581453
|
+
const matches = _slashItems
|
|
581454
|
+
.filter(c => c.name.toLowerCase().startsWith(q))
|
|
581455
|
+
.slice(0, 12);
|
|
581456
|
+
if (matches.length === 0) { p.style.display = 'none'; return; }
|
|
581457
|
+
if (_slashSelectedIdx >= matches.length) _slashSelectedIdx = 0;
|
|
581458
|
+
const list = document.getElementById('slash-palette-list');
|
|
581459
|
+
if (!list) return;
|
|
581460
|
+
list.innerHTML = matches.map((c, i) => {
|
|
581461
|
+
const sel = (i === _slashSelectedIdx) ? ' selected' : '';
|
|
581462
|
+
const safeDesc = String(c.desc || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
581463
|
+
const safeName = c.name.replace(/[^a-zA-Z0-9_:.\\-]/g, '');
|
|
581464
|
+
return '<div class="slash-row' + sel + '" data-cmd="' + safeName + '" onclick="_slashPaletteAcceptByName(\\'' + safeName + '\\')"><span class="slash-name">/' + safeName + '</span><span class="slash-desc">' + safeDesc + '</span></div>';
|
|
581465
|
+
}).join('');
|
|
581466
|
+
p.style.display = 'block';
|
|
581467
|
+
}
|
|
581468
|
+
|
|
581469
|
+
function _slashPaletteMove(delta) {
|
|
581470
|
+
const list = document.getElementById('slash-palette-list');
|
|
581471
|
+
if (!list) return;
|
|
581472
|
+
const rows = list.querySelectorAll('.slash-row');
|
|
581473
|
+
if (rows.length === 0) return;
|
|
581474
|
+
_slashSelectedIdx = (_slashSelectedIdx + delta + rows.length) % rows.length;
|
|
581475
|
+
rows.forEach((r, i) => r.classList.toggle('selected', i === _slashSelectedIdx));
|
|
581476
|
+
rows[_slashSelectedIdx].scrollIntoView({ block: 'nearest' });
|
|
581477
|
+
}
|
|
581478
|
+
|
|
581479
|
+
function _slashPaletteAccept() {
|
|
581480
|
+
const list = document.getElementById('slash-palette-list');
|
|
581481
|
+
if (!list) return;
|
|
581482
|
+
const row = list.querySelectorAll('.slash-row')[_slashSelectedIdx];
|
|
581483
|
+
if (!row) return;
|
|
581484
|
+
const name = row.getAttribute('data-cmd');
|
|
581485
|
+
_slashPaletteAcceptByName(name);
|
|
581486
|
+
}
|
|
581487
|
+
|
|
581488
|
+
function _slashPaletteAcceptByName(name) {
|
|
581489
|
+
if (!name) return;
|
|
581490
|
+
// Replace just the slash-token in the input, preserving any args the
|
|
581491
|
+
// user may have already typed after a space.
|
|
581492
|
+
const v = input.value || '';
|
|
581493
|
+
const idx = v.indexOf(' ');
|
|
581494
|
+
const trailing = idx >= 0 ? v.slice(idx) : '';
|
|
581495
|
+
input.value = '/' + name + (trailing || ' ');
|
|
581496
|
+
closeSlashPalette();
|
|
581497
|
+
input.focus();
|
|
581498
|
+
// Cursor after the inserted command name.
|
|
581499
|
+
try {
|
|
581500
|
+
const pos = ('/' + name + ' ').length;
|
|
581501
|
+
input.setSelectionRange(pos, pos);
|
|
581502
|
+
} catch {}
|
|
581503
|
+
}
|
|
581504
|
+
|
|
581505
|
+
// Attachments
|
|
581506
|
+
function handleAttachInput(fileList) {
|
|
581507
|
+
if (!fileList || fileList.length === 0) return;
|
|
581508
|
+
for (const f of fileList) {
|
|
581509
|
+
_attachments.push({ name: f.name, size: f.size || 0, file: f });
|
|
581510
|
+
}
|
|
581511
|
+
_renderAttachChips();
|
|
581512
|
+
}
|
|
581513
|
+
|
|
581514
|
+
function _renderAttachChips() {
|
|
581515
|
+
const row = document.getElementById('attach-chips');
|
|
581516
|
+
if (!row) return;
|
|
581517
|
+
if (_attachments.length === 0) {
|
|
581518
|
+
row.innerHTML = '';
|
|
581519
|
+
row.style.display = 'none';
|
|
581520
|
+
return;
|
|
581521
|
+
}
|
|
581522
|
+
row.style.display = 'flex';
|
|
581523
|
+
row.innerHTML = _attachments.map((a, i) => {
|
|
581524
|
+
const safeName = String(a.name).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
581525
|
+
const sizeKB = a.size > 0 ? ' (' + Math.round(a.size / 1024) + 'KB)' : '';
|
|
581526
|
+
return '<span class="chip"><span class="name" title="' + safeName + '">' + safeName + sizeKB + '</span><button class="x" onclick="_removeAttachment(' + i + ')" title="Remove">×</button></span>';
|
|
581527
|
+
}).join('');
|
|
581528
|
+
}
|
|
581529
|
+
function _removeAttachment(i) {
|
|
581530
|
+
_attachments.splice(i, 1);
|
|
581531
|
+
_renderAttachChips();
|
|
581532
|
+
}
|
|
581533
|
+
|
|
581534
|
+
// Drag & drop on the input row
|
|
581535
|
+
(function _wireDropZone() {
|
|
581536
|
+
const row = document.getElementById('input-row');
|
|
581537
|
+
if (!row) return;
|
|
581538
|
+
['dragover', 'dragenter'].forEach(ev => row.addEventListener(ev, (e) => {
|
|
581539
|
+
e.preventDefault(); e.stopPropagation();
|
|
581540
|
+
row.classList.add('dragover');
|
|
581541
|
+
}));
|
|
581542
|
+
['dragleave', 'drop'].forEach(ev => row.addEventListener(ev, (e) => {
|
|
581543
|
+
e.preventDefault(); e.stopPropagation();
|
|
581544
|
+
row.classList.remove('dragover');
|
|
581545
|
+
}));
|
|
581546
|
+
row.addEventListener('drop', (e) => {
|
|
581547
|
+
const files = e.dataTransfer && e.dataTransfer.files;
|
|
581548
|
+
if (files && files.length) handleAttachInput(files);
|
|
581549
|
+
});
|
|
581550
|
+
})();
|
|
581551
|
+
|
|
581552
|
+
// Mic toggle — defers to the existing voice-tab plumbing; this is
|
|
581553
|
+
// a shortcut from the input toolbar so users don't have to navigate
|
|
581554
|
+
// to the voice tab to toggle voicechat.
|
|
581555
|
+
function toggleVoiceMicFromInput() {
|
|
581556
|
+
// Two paths to flip the live mic, depending on which one is wired
|
|
581557
|
+
// in the current session.
|
|
581558
|
+
let triggered = false;
|
|
581559
|
+
try {
|
|
581560
|
+
if (typeof toggleVoiceChat === 'function') { toggleVoiceChat(); triggered = true; }
|
|
581561
|
+
else if (typeof toggleListenEngine === 'function') { toggleListenEngine(); triggered = true; }
|
|
581562
|
+
else if (typeof toggleVoice === 'function') { toggleVoice(); triggered = true; }
|
|
581563
|
+
} catch {}
|
|
581564
|
+
// As a last resort, jump the user to the voice tab so they can
|
|
581565
|
+
// engage from there.
|
|
581566
|
+
if (!triggered) { try { switchTab('voice'); } catch {} }
|
|
581567
|
+
// Active-state visualization on the toolbar mic icon.
|
|
581568
|
+
const btn = document.getElementById('mic-btn');
|
|
581569
|
+
if (btn) btn.classList.toggle('active');
|
|
581570
|
+
}
|
|
581571
|
+
|
|
581572
|
+
// Model picker mirror — keep #input-model-mini in sync with the legacy
|
|
581573
|
+
// #model-select so OWUI-4's compact picker reflects the truth.
|
|
581574
|
+
(function _mirrorModelPicker() {
|
|
581575
|
+
const sync = () => {
|
|
581576
|
+
const src = document.getElementById('model-select');
|
|
581577
|
+
const dst = document.getElementById('input-model-mini');
|
|
581578
|
+
if (!src || !dst) return;
|
|
581579
|
+
if (src.options.length !== dst.options.length) {
|
|
581580
|
+
dst.innerHTML = '';
|
|
581581
|
+
for (const o of src.options) {
|
|
581582
|
+
const opt = document.createElement('option');
|
|
581583
|
+
opt.value = o.value;
|
|
581584
|
+
opt.textContent = o.textContent;
|
|
581585
|
+
dst.appendChild(opt);
|
|
581586
|
+
}
|
|
581587
|
+
}
|
|
581588
|
+
dst.value = src.value;
|
|
581589
|
+
};
|
|
581590
|
+
// Run after every load + on a small interval.
|
|
581591
|
+
setTimeout(sync, 600);
|
|
581592
|
+
setInterval(sync, 4000);
|
|
581593
|
+
})();
|
|
581594
|
+
|
|
581595
|
+
// Close palette when the user clicks elsewhere.
|
|
581596
|
+
document.addEventListener('click', (e) => {
|
|
581597
|
+
const p = document.getElementById('slash-palette');
|
|
581598
|
+
if (!p) return;
|
|
581599
|
+
if (!p.contains(e.target) && e.target !== input && e.target.id !== 'slash-btn') {
|
|
581600
|
+
p.style.display = 'none';
|
|
581601
|
+
}
|
|
581602
|
+
});
|
|
581603
|
+
|
|
579418
581604
|
input.focus();
|
|
579419
581605
|
</script>
|
|
581606
|
+
</div><!-- /#oa-main -->
|
|
581607
|
+
</div><!-- /#oa-shell -->
|
|
579420
581608
|
</body>
|
|
579421
581609
|
</html>`;
|
|
579422
581610
|
}
|
|
@@ -580986,12 +583174,11 @@ import { fileURLToPath as fileURLToPath17 } from "node:url";
|
|
|
580986
583174
|
import { dirname as dirname33, join as join109, resolve as resolve37 } from "node:path";
|
|
580987
583175
|
import { homedir as homedir40 } from "node:os";
|
|
580988
583176
|
import { spawn as spawn25, execSync as execSync55 } from "node:child_process";
|
|
580989
|
-
import { mkdirSync as mkdirSync59, writeFileSync as writeFileSync52, readFileSync as readFileSync74, readdirSync as readdirSync30, existsSync as existsSync92, watch as fsWatch3 } from "node:fs";
|
|
583177
|
+
import { mkdirSync as mkdirSync59, writeFileSync as writeFileSync52, readFileSync as readFileSync74, readdirSync as readdirSync30, existsSync as existsSync92, watch as fsWatch3, renameSync as renameSync8, unlinkSync as unlinkSync24 } from "node:fs";
|
|
580990
583178
|
import { randomBytes as randomBytes21, randomUUID as randomUUID14 } from "node:crypto";
|
|
580991
583179
|
import { createHash as createHash14 } from "node:crypto";
|
|
580992
583180
|
function getVersion3() {
|
|
580993
583181
|
try {
|
|
580994
|
-
const require3 = createRequire4(import.meta.url);
|
|
580995
583182
|
const thisDir = dirname33(fileURLToPath17(import.meta.url));
|
|
580996
583183
|
const candidates = [
|
|
580997
583184
|
join109(thisDir, "..", "package.json"),
|
|
@@ -581707,19 +583894,18 @@ function autoSeedTodosFromPrompt(prompt) {
|
|
|
581707
583894
|
return [];
|
|
581708
583895
|
}
|
|
581709
583896
|
function atomicJobWrite(dir, id, job) {
|
|
581710
|
-
const fs7 = __require("node:fs");
|
|
581711
583897
|
const finalPath = join109(dir, `${id}.json`);
|
|
581712
583898
|
const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}`;
|
|
581713
583899
|
try {
|
|
581714
|
-
|
|
581715
|
-
|
|
583900
|
+
writeFileSync52(tmpPath, JSON.stringify(job, null, 2), "utf-8");
|
|
583901
|
+
renameSync8(tmpPath, finalPath);
|
|
581716
583902
|
} catch {
|
|
581717
583903
|
try {
|
|
581718
|
-
|
|
583904
|
+
writeFileSync52(finalPath, JSON.stringify(job, null, 2), "utf-8");
|
|
581719
583905
|
} catch {
|
|
581720
583906
|
}
|
|
581721
583907
|
try {
|
|
581722
|
-
|
|
583908
|
+
unlinkSync24(tmpPath);
|
|
581723
583909
|
} catch {
|
|
581724
583910
|
}
|
|
581725
583911
|
}
|
|
@@ -582801,12 +584987,11 @@ function readUpdateState() {
|
|
|
582801
584987
|
function writeUpdateState(state) {
|
|
582802
584988
|
try {
|
|
582803
584989
|
const dir = join109(homedir40(), ".open-agents");
|
|
582804
|
-
|
|
582805
|
-
fs7.mkdirSync(dir, { recursive: true });
|
|
584990
|
+
mkdirSync59(dir, { recursive: true });
|
|
582806
584991
|
const finalPath = updateStateFile();
|
|
582807
584992
|
const tmpPath = `${finalPath}.tmp.${process.pid}`;
|
|
582808
|
-
|
|
582809
|
-
|
|
584993
|
+
writeFileSync52(tmpPath, JSON.stringify(state, null, 2), "utf-8");
|
|
584994
|
+
renameSync8(tmpPath, finalPath);
|
|
582810
584995
|
} catch {
|
|
582811
584996
|
}
|
|
582812
584997
|
}
|
|
@@ -582843,10 +585028,10 @@ async function handleV1Update(req2, res, requestId) {
|
|
|
582843
585028
|
from: currentVersion,
|
|
582844
585029
|
to: targetVersion
|
|
582845
585030
|
}, { subject: req2._authUser ?? "anonymous" });
|
|
582846
|
-
const fs7 =
|
|
585031
|
+
const fs7 = require3("node:fs");
|
|
582847
585032
|
const nodeBin = process.execPath;
|
|
582848
585033
|
const nodeDir = dirname33(nodeBin);
|
|
582849
|
-
const { execSync: es } =
|
|
585034
|
+
const { execSync: es } = require3("node:child_process");
|
|
582850
585035
|
const isWin2 = process.platform === "win32";
|
|
582851
585036
|
let npmBin = "";
|
|
582852
585037
|
for (const candidate of isWin2 ? [join109(nodeDir, "npm.cmd"), join109(nodeDir, "npm")] : [join109(nodeDir, "npm"), "/usr/local/bin/npm", "/usr/bin/npm"]) {
|
|
@@ -583912,9 +586097,9 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
|
|
|
583912
586097
|
return;
|
|
583913
586098
|
}
|
|
583914
586099
|
if (pathname === "/v1/system" && method === "GET") {
|
|
583915
|
-
const os8 =
|
|
586100
|
+
const os8 = require3("node:os");
|
|
583916
586101
|
const version4 = getVersion3();
|
|
583917
|
-
const { execSync: es } =
|
|
586102
|
+
const { execSync: es } = require3("node:child_process");
|
|
583918
586103
|
let gpus = [];
|
|
583919
586104
|
try {
|
|
583920
586105
|
const nv = es("nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits", { encoding: "utf8", timeout: 5e3, stdio: "pipe" });
|
|
@@ -584138,7 +586323,7 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
|
|
|
584138
586323
|
return;
|
|
584139
586324
|
}
|
|
584140
586325
|
const { tmpdir: tmpdir22 } = await import("node:os");
|
|
584141
|
-
const { writeFileSync: writeFileSync56, unlinkSync:
|
|
586326
|
+
const { writeFileSync: writeFileSync56, unlinkSync: unlinkSync25 } = await import("node:fs");
|
|
584142
586327
|
const { join: pjoin } = await import("node:path");
|
|
584143
586328
|
const tmpPath = pjoin(tmpdir22(), `oa-clone-upload-${Date.now()}-${safeName}`);
|
|
584144
586329
|
writeFileSync56(tmpPath, buf);
|
|
@@ -584153,7 +586338,7 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
|
|
|
584153
586338
|
});
|
|
584154
586339
|
} finally {
|
|
584155
586340
|
try {
|
|
584156
|
-
|
|
586341
|
+
unlinkSync25(tmpPath);
|
|
584157
586342
|
} catch {
|
|
584158
586343
|
}
|
|
584159
586344
|
}
|
|
@@ -584795,8 +586980,8 @@ data: ${JSON.stringify(data)}
|
|
|
584795
586980
|
} catch {
|
|
584796
586981
|
}
|
|
584797
586982
|
try {
|
|
584798
|
-
const { execSync: es, spawnSync: ss } =
|
|
584799
|
-
const home = process.env.HOME ||
|
|
586983
|
+
const { execSync: es, spawnSync: ss } = require3("node:child_process");
|
|
586984
|
+
const home = process.env.HOME || require3("node:os").homedir();
|
|
584800
586985
|
const userDir = `${home}/.config/systemd/user`;
|
|
584801
586986
|
for (const suffix of [".timer", ".service"]) {
|
|
584802
586987
|
try {
|
|
@@ -585795,7 +587980,7 @@ function listScheduledTasks() {
|
|
|
585795
587980
|
function listOaUserTimers() {
|
|
585796
587981
|
const out = [];
|
|
585797
587982
|
try {
|
|
585798
|
-
const { execSync: es } =
|
|
587983
|
+
const { execSync: es } = require3("node:child_process");
|
|
585799
587984
|
const files = es("systemctl --user list-unit-files --type=timer --no-legend", { encoding: "utf8", stdio: "pipe" }).trim().split("\n");
|
|
585800
587985
|
const enabledMap = /* @__PURE__ */ new Map();
|
|
585801
587986
|
for (const line of files) {
|
|
@@ -585893,7 +588078,7 @@ function deleteScheduledById(id) {
|
|
|
585893
588078
|
if (id) candidates.push(id);
|
|
585894
588079
|
if (typeof entry?.id === "string" && entry.id && !candidates.includes(entry.id)) candidates.push(entry.id);
|
|
585895
588080
|
try {
|
|
585896
|
-
const { createHash: createHash15 } =
|
|
588081
|
+
const { createHash: createHash15 } = require3("node:crypto");
|
|
585897
588082
|
const fallback = createHash15("sha1").update(`${target.file}#${target.index}`).digest("hex").slice(0, 16);
|
|
585898
588083
|
if (!candidates.includes(fallback)) candidates.push(fallback);
|
|
585899
588084
|
} catch {
|
|
@@ -585942,7 +588127,7 @@ function removeCronByDirectory(dir) {
|
|
|
585942
588127
|
function killProcessGroups(pids, pattern) {
|
|
585943
588128
|
const killed = [];
|
|
585944
588129
|
try {
|
|
585945
|
-
const { execSync: es } =
|
|
588130
|
+
const { execSync: es } = require3("node:child_process");
|
|
585946
588131
|
const targets = /* @__PURE__ */ new Map();
|
|
585947
588132
|
if (pids && pids.length > 0) {
|
|
585948
588133
|
for (const pid of pids) {
|
|
@@ -586003,8 +588188,8 @@ function killProcessGroups(pids, pattern) {
|
|
|
586003
588188
|
function disableAllOaTimers() {
|
|
586004
588189
|
let disabled = 0;
|
|
586005
588190
|
try {
|
|
586006
|
-
const { execSync: es } =
|
|
586007
|
-
const home = process.env.HOME ||
|
|
588191
|
+
const { execSync: es } = require3("node:child_process");
|
|
588192
|
+
const home = process.env.HOME || require3("node:os").homedir();
|
|
586008
588193
|
const userDir = `${home}/.config/systemd/user`;
|
|
586009
588194
|
const timers = listOaUserTimers();
|
|
586010
588195
|
for (const t2 of timers) {
|
|
@@ -586055,7 +588240,7 @@ function removeAllOaCrons() {
|
|
|
586055
588240
|
function listMatchingProcesses(pattern) {
|
|
586056
588241
|
const list = [];
|
|
586057
588242
|
try {
|
|
586058
|
-
const { execSync: es } =
|
|
588243
|
+
const { execSync: es } = require3("node:child_process");
|
|
586059
588244
|
const re = new RegExp(pattern, "i");
|
|
586060
588245
|
const ps = es("ps -eo pid,etimes,pcpu,pmem,command", { encoding: "utf8", stdio: "pipe" });
|
|
586061
588246
|
for (const line of ps.split("\n")) {
|
|
@@ -586075,7 +588260,7 @@ function listMatchingProcesses(pattern) {
|
|
|
586075
588260
|
}
|
|
586076
588261
|
function sampleGpuUtil() {
|
|
586077
588262
|
try {
|
|
586078
|
-
const { execSync: es } =
|
|
588263
|
+
const { execSync: es } = require3("node:child_process");
|
|
586079
588264
|
const out = es("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits", { encoding: "utf8", timeout: 3e3, stdio: "pipe" });
|
|
586080
588265
|
const arr = [];
|
|
586081
588266
|
for (const line of out.trim().split("\n")) {
|
|
@@ -586092,7 +588277,7 @@ function sampleGpuUtil() {
|
|
|
586092
588277
|
}
|
|
586093
588278
|
function getCurrentCrontabLines() {
|
|
586094
588279
|
try {
|
|
586095
|
-
const { execSync: es } =
|
|
588280
|
+
const { execSync: es } = require3("node:child_process");
|
|
586096
588281
|
return es("crontab -l 2>/dev/null", { encoding: "utf8", stdio: "pipe" }).split("\n");
|
|
586097
588282
|
} catch {
|
|
586098
588283
|
return [];
|
|
@@ -586175,7 +588360,7 @@ function reconcileScheduledTasks(apply) {
|
|
|
586175
588360
|
}
|
|
586176
588361
|
function findOaBinary4() {
|
|
586177
588362
|
try {
|
|
586178
|
-
const { execSync: es } =
|
|
588363
|
+
const { execSync: es } = require3("node:child_process");
|
|
586179
588364
|
for (const cmd of ["oa", "open-agents"]) {
|
|
586180
588365
|
try {
|
|
586181
588366
|
const p2 = es(`which ${cmd} 2>/dev/null`, { encoding: "utf8", stdio: "pipe" }).trim();
|
|
@@ -586192,7 +588377,7 @@ function getCrontabLines() {
|
|
|
586192
588377
|
}
|
|
586193
588378
|
function writeCrontabLines(lines) {
|
|
586194
588379
|
try {
|
|
586195
|
-
const { spawnSync: spawnSync6 } =
|
|
588380
|
+
const { spawnSync: spawnSync6 } = require3("node:child_process");
|
|
586196
588381
|
const result = spawnSync6("crontab", ["-"], { input: lines.join("\n") + "\n", encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
586197
588382
|
if (result.status !== 0) throw new Error(`crontab exited ${result.status}: ${String(result.stderr || "").trim()}`);
|
|
586198
588383
|
} catch (e2) {
|
|
@@ -586294,7 +588479,7 @@ WantedBy=timers.target
|
|
|
586294
588479
|
writeFileSync52(svc, svcText);
|
|
586295
588480
|
writeFileSync52(tim, timText);
|
|
586296
588481
|
try {
|
|
586297
|
-
const { execSync: es } =
|
|
588482
|
+
const { execSync: es } = require3("node:child_process");
|
|
586298
588483
|
es("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
586299
588484
|
es(`systemctl --user enable --now ${unitBase}.timer`, { stdio: "pipe" });
|
|
586300
588485
|
} catch {
|
|
@@ -586321,7 +588506,7 @@ WantedBy=timers.target
|
|
|
586321
588506
|
function listUserServices(pattern) {
|
|
586322
588507
|
const out = [];
|
|
586323
588508
|
try {
|
|
586324
|
-
const { execSync: es } =
|
|
588509
|
+
const { execSync: es } = require3("node:child_process");
|
|
586325
588510
|
const files = es("systemctl --user list-unit-files --type=service --no-legend", { encoding: "utf8", stdio: "pipe" }).trim().split("\n");
|
|
586326
588511
|
const enabledMap = /* @__PURE__ */ new Map();
|
|
586327
588512
|
for (const line of files) {
|
|
@@ -586347,7 +588532,7 @@ function listUserServices(pattern) {
|
|
|
586347
588532
|
}
|
|
586348
588533
|
function userServiceAction(unit, action) {
|
|
586349
588534
|
try {
|
|
586350
|
-
const { execSync: es } =
|
|
588535
|
+
const { execSync: es } = require3("node:child_process");
|
|
586351
588536
|
if (action === "stop") {
|
|
586352
588537
|
es(`systemctl --user stop ${unit}`, { stdio: "pipe" });
|
|
586353
588538
|
return true;
|
|
@@ -586515,8 +588700,8 @@ function startApiServer(options2 = {}) {
|
|
|
586515
588700
|
const job = JSON.parse(readFileSync74(jobPath, "utf-8"));
|
|
586516
588701
|
const jobTime = new Date(job.startedAt ?? job.completedAt ?? 0).getTime();
|
|
586517
588702
|
if (jobTime > 0 && jobTime < cutoff && job.status !== "running") {
|
|
586518
|
-
const { unlinkSync:
|
|
586519
|
-
|
|
588703
|
+
const { unlinkSync: unlinkSync25 } = require3("node:fs");
|
|
588704
|
+
unlinkSync25(jobPath);
|
|
586520
588705
|
}
|
|
586521
588706
|
} catch {
|
|
586522
588707
|
}
|
|
@@ -586837,7 +589022,7 @@ function startApiServer(options2 = {}) {
|
|
|
586837
589022
|
log22(` Port ${port} in use — reclaiming...
|
|
586838
589023
|
`);
|
|
586839
589024
|
try {
|
|
586840
|
-
const { execSync: es } =
|
|
589025
|
+
const { execSync: es } = require3("node:child_process");
|
|
586841
589026
|
const pids = es(`lsof -ti :${port} 2>/dev/null || fuser ${port}/tcp 2>/dev/null || true`, { encoding: "utf8" }).trim().split(/[\n\s]+/).filter(Boolean);
|
|
586842
589027
|
for (const pid of pids) {
|
|
586843
589028
|
const numPid = parseInt(pid, 10);
|
|
@@ -587134,8 +589319,8 @@ function removeCronByMarker(id) {
|
|
|
587134
589319
|
}
|
|
587135
589320
|
function disableUserTimerById(id) {
|
|
587136
589321
|
try {
|
|
587137
|
-
const { execSync: es } =
|
|
587138
|
-
const home = process.env.HOME ||
|
|
589322
|
+
const { execSync: es } = require3("node:child_process");
|
|
589323
|
+
const home = process.env.HOME || require3("node:os").homedir();
|
|
587139
589324
|
const userDir = `${home}/.config/systemd/user`;
|
|
587140
589325
|
const names = [`oa-${id}`, `oa-sched-${id}`, id];
|
|
587141
589326
|
for (const n2 of names) {
|
|
@@ -587164,7 +589349,7 @@ function disableUserTimerById(id) {
|
|
|
587164
589349
|
function setTimerEnabled(name10, enabled2) {
|
|
587165
589350
|
try {
|
|
587166
589351
|
const unit = `${name10}.timer`;
|
|
587167
|
-
const { execSync: es } =
|
|
589352
|
+
const { execSync: es } = require3("node:child_process");
|
|
587168
589353
|
if (enabled2) es(`systemctl --user enable --now ${unit}`, { stdio: "pipe" });
|
|
587169
589354
|
else es(`systemctl --user disable --now ${unit}`, { stdio: "pipe" });
|
|
587170
589355
|
return true;
|
|
@@ -587172,7 +589357,7 @@ function setTimerEnabled(name10, enabled2) {
|
|
|
587172
589357
|
return false;
|
|
587173
589358
|
}
|
|
587174
589359
|
}
|
|
587175
|
-
var endpointRegistry, modelRouteMap, endpointUsage, metrics, startedAt, runningProcesses, perKeyUsage, CRON_MARKER2;
|
|
589360
|
+
var require3, endpointRegistry, modelRouteMap, endpointUsage, metrics, startedAt, runningProcesses, perKeyUsage, CRON_MARKER2;
|
|
587176
589361
|
var init_serve = __esm({
|
|
587177
589362
|
"packages/cli/src/api/serve.ts"() {
|
|
587178
589363
|
"use strict";
|
|
@@ -587200,6 +589385,7 @@ var init_serve = __esm({
|
|
|
587200
589385
|
init_profiles();
|
|
587201
589386
|
init_docker();
|
|
587202
589387
|
init_typed_node_events();
|
|
589388
|
+
require3 = createRequire4(import.meta.url);
|
|
587203
589389
|
endpointRegistry = [];
|
|
587204
589390
|
modelRouteMap = /* @__PURE__ */ new Map();
|
|
587205
589391
|
endpointUsage = /* @__PURE__ */ new Map();
|
|
@@ -587237,7 +589423,7 @@ function formatTimeAgo2(date) {
|
|
|
587237
589423
|
}
|
|
587238
589424
|
function getVersion4() {
|
|
587239
589425
|
try {
|
|
587240
|
-
const
|
|
589426
|
+
const require4 = createRequire5(import.meta.url);
|
|
587241
589427
|
const thisDir = dirname34(fileURLToPath18(import.meta.url));
|
|
587242
589428
|
const candidates = [
|
|
587243
589429
|
join110(thisDir, "..", "package.json"),
|
|
@@ -587246,7 +589432,7 @@ function getVersion4() {
|
|
|
587246
589432
|
];
|
|
587247
589433
|
for (const pkgPath of candidates) {
|
|
587248
589434
|
if (existsSync93(pkgPath)) {
|
|
587249
|
-
const pkg =
|
|
589435
|
+
const pkg = require4(pkgPath);
|
|
587250
589436
|
if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli" || pkg.name === "@open-agents/monorepo") {
|
|
587251
589437
|
return pkg.version ?? "0.0.0";
|
|
587252
589438
|
}
|
|
@@ -594979,9 +597165,9 @@ init_spinner();
|
|
|
594979
597165
|
init_output();
|
|
594980
597166
|
function getVersion5() {
|
|
594981
597167
|
try {
|
|
594982
|
-
const
|
|
597168
|
+
const require4 = createRequire6(import.meta.url);
|
|
594983
597169
|
const pkgPath = join114(dirname35(fileURLToPath19(import.meta.url)), "..", "package.json");
|
|
594984
|
-
const pkg =
|
|
597170
|
+
const pkg = require4(pkgPath);
|
|
594985
597171
|
return pkg.version;
|
|
594986
597172
|
} catch {
|
|
594987
597173
|
return "0.1.0";
|