open-agents-ai 0.187.455 → 0.187.457
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 +2975 -560
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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
|
|
|
575541
|
-
<!--
|
|
575542
|
-
|
|
575543
|
-
|
|
575544
|
-
|
|
575545
|
-
|
|
575546
|
-
|
|
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
|
+
|
|
576448
|
+
<!-- Lower sub-row: existing input + send/stop -->
|
|
576449
|
+
<div id="input-row">
|
|
576450
|
+
<span id="system-prompt-toggle" onclick="toggleSystemPrompt()" style="display:none">sys</span>
|
|
576451
|
+
<textarea id="input-area" placeholder="Type a message..." rows="1"></textarea>
|
|
576452
|
+
<button id="send-btn" onclick="sendMessage()">send</button>
|
|
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>
|
|
@@ -575872,7 +576886,6 @@ const statusEl = document.getElementById('status');
|
|
|
575872
576886
|
let streaming = false;
|
|
575873
576887
|
let messages = [];
|
|
575874
576888
|
let chatAbortController = null; // for stop button
|
|
575875
|
-
let chatAbortController = null; // for stop button
|
|
575876
576889
|
// WO-CHAT-RESUME — interval id for the in-flight job poller. Set when we
|
|
575877
576890
|
// detect an active job on reload; cleared when the job reaches a terminal
|
|
575878
576891
|
// state OR the user navigates away.
|
|
@@ -575918,7 +576931,10 @@ if (conv) {
|
|
|
575918
576931
|
// Auto-resize textarea + WO-CHAT-CHECKIN typing detection
|
|
575919
576932
|
input.addEventListener('input', () => {
|
|
575920
576933
|
input.style.height = 'auto';
|
|
575921
|
-
|
|
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 {}
|
|
575922
576938
|
// While a run is streaming, swap stop button → check-in button as
|
|
575923
576939
|
// soon as the user types anything. When the input is cleared, swap back.
|
|
575924
576940
|
if (streaming) {
|
|
@@ -575934,6 +576950,22 @@ input.addEventListener('input', () => {
|
|
|
575934
576950
|
}
|
|
575935
576951
|
});
|
|
575936
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
|
+
}
|
|
575937
576969
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
575938
576970
|
e.preventDefault();
|
|
575939
576971
|
// While a run is streaming, Enter dispatches a check-in instead of
|
|
@@ -576042,29 +577074,106 @@ function persistSelectedModel() {
|
|
|
576042
577074
|
function addMessage(role, content) {
|
|
576043
577075
|
const div = document.createElement('div');
|
|
576044
577076
|
div.className = 'msg ' + role;
|
|
576045
|
-
|
|
576046
|
-
//
|
|
576047
|
-
//
|
|
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
|
+
}
|
|
576048
577088
|
if (role === 'assistant' || (role === 'user' && looksLikeMarkdown(content))) {
|
|
576049
|
-
|
|
577089
|
+
host.innerHTML = renderMarkdown(content);
|
|
576050
577090
|
} else {
|
|
576051
|
-
|
|
577091
|
+
host.textContent = content;
|
|
576052
577092
|
}
|
|
576053
|
-
//
|
|
577093
|
+
// OWUI-3: rich per-message action row on assistant messages.
|
|
576054
577094
|
if (role === 'assistant') {
|
|
576055
577095
|
const actions = document.createElement('div');
|
|
576056
577096
|
actions.className = 'msg-actions';
|
|
576057
|
-
const
|
|
576058
|
-
|
|
576059
|
-
|
|
576060
|
-
|
|
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
|
+
}));
|
|
576061
577120
|
div.appendChild(actions);
|
|
576062
577121
|
}
|
|
576063
577122
|
conv.appendChild(div);
|
|
577123
|
+
// OWUI-3: schedule syntax highlight for any new code blocks.
|
|
577124
|
+
try { _highlightCodeBlocks(div); } catch {}
|
|
576064
577125
|
maybeAutoScroll();
|
|
576065
577126
|
return div;
|
|
576066
577127
|
}
|
|
576067
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
|
+
|
|
576068
577177
|
// WO-TASK-02 — heuristic detection: does this string contain markdown
|
|
576069
577178
|
// constructs that warrant parsing? Used for .msg.user content where we
|
|
576070
577179
|
// don't want to render every plain string as HTML, but we DO want to
|
|
@@ -576116,14 +577225,14 @@ function renderMarkdown(text) {
|
|
|
576116
577225
|
// Headers — h1..h6
|
|
576117
577226
|
text = text.replace(/(^|\\n)(#{1,6}) (.+?)(\\n|$)/g, (_, pre, hashes, body, post) => {
|
|
576118
577227
|
const lvl = hashes.length;
|
|
576119
|
-
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);
|
|
576120
577229
|
});
|
|
576121
577230
|
// Blockquote
|
|
576122
577231
|
text = text.replace(/(^|\\n)> (.+?)(\\n|$)/g, (_, pre, body, post) => {
|
|
576123
|
-
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);
|
|
576124
577233
|
});
|
|
576125
577234
|
// Horizontal rule
|
|
576126
|
-
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');
|
|
576127
577236
|
// Unordered lists — collapse consecutive items into a single <ul>
|
|
576128
577237
|
text = text.replace(/(?:(^|\\n)[-*+] [^\\n]+)+/g, m => {
|
|
576129
577238
|
const items = m.trim().split(/\\n/).map(l => l.replace(/^[-*+] /, '')).map(l => '<li>' + l + '</li>').join('');
|
|
@@ -576147,7 +577256,7 @@ function renderMarkdown(text) {
|
|
|
576147
577256
|
text = text.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_, label, url) => {
|
|
576148
577257
|
// Only allow http(s) or relative URLs to prevent javascript: links
|
|
576149
577258
|
const safe = /^(https?:\\/\\/|\\/|\\.\\.?\\/|#)/.test(url) ? url : '#';
|
|
576150
|
-
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>';
|
|
576151
577260
|
});
|
|
576152
577261
|
|
|
576153
577262
|
// 5) Re-insert code blocks
|
|
@@ -576184,7 +577293,7 @@ function appendExpandableContent(parent, fullText, opts) {
|
|
|
576184
577293
|
if (isLong) {
|
|
576185
577294
|
const btn = document.createElement('button');
|
|
576186
577295
|
btn.type = 'button';
|
|
576187
|
-
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;';
|
|
576188
577297
|
btn.textContent = 'Show more (' + text.length + ' chars)';
|
|
576189
577298
|
let expanded = false;
|
|
576190
577299
|
btn.addEventListener('click', (e) => {
|
|
@@ -576207,9 +577316,9 @@ function appendExpandableContent(parent, fullText, opts) {
|
|
|
576207
577316
|
// session message shape ({role:'tool_call', tool, args}).
|
|
576208
577317
|
function renderToolCallEvent(parent, chunkLike) {
|
|
576209
577318
|
const details = document.createElement('details');
|
|
576210
|
-
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';
|
|
576211
577320
|
const summary = document.createElement('summary');
|
|
576212
|
-
summary.style.cssText = 'padding:4px 8px;color
|
|
577321
|
+
summary.style.cssText = 'padding:4px 8px;color:var(--color-brand);cursor:pointer';
|
|
576213
577322
|
|
|
576214
577323
|
const toolName = chunkLike.tool || 'tool';
|
|
576215
577324
|
let a = (chunkLike.args && typeof chunkLike.args === 'object') ? chunkLike.args : {};
|
|
@@ -576254,18 +577363,18 @@ function renderToolCallEvent(parent, chunkLike) {
|
|
|
576254
577363
|
|
|
576255
577364
|
if (a && typeof a === 'object') {
|
|
576256
577365
|
const argsDiv = document.createElement('div');
|
|
576257
|
-
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)';
|
|
576258
577367
|
if (toolName === 'todo_write' && Array.isArray(a.todos)) {
|
|
576259
577368
|
for (const t of a.todos) {
|
|
576260
577369
|
if (!t || typeof t !== 'object') continue;
|
|
576261
577370
|
const row = document.createElement('div');
|
|
576262
577371
|
row.style.cssText = 'padding:2px 0';
|
|
576263
577372
|
let mark = '\\u25CB';
|
|
576264
|
-
let color = '
|
|
577373
|
+
let color = 'var(--color-fg-subtle)';
|
|
576265
577374
|
if (t.status === 'completed') { mark = '\\u25C9'; color = '#4a7a4a'; }
|
|
576266
|
-
else if (t.status === 'in_progress') { mark = '\\u25D0'; color = '
|
|
576267
|
-
else if (t.status === 'blocked') { mark = '\\u25CD'; color = '
|
|
576268
|
-
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>' : '');
|
|
576269
577378
|
argsDiv.appendChild(row);
|
|
576270
577379
|
}
|
|
576271
577380
|
} else {
|
|
@@ -576279,13 +577388,13 @@ function renderToolCallEvent(parent, chunkLike) {
|
|
|
576279
577388
|
else { try { vs = JSON.stringify(v, null, 2); } catch { vs = '[object]'; } }
|
|
576280
577389
|
|
|
576281
577390
|
const keyEl = document.createElement('span');
|
|
576282
|
-
keyEl.style.cssText = 'color
|
|
577391
|
+
keyEl.style.cssText = 'color:var(--color-brand);min-width:60px;flex-shrink:0';
|
|
576283
577392
|
keyEl.textContent = k;
|
|
576284
577393
|
row.appendChild(keyEl);
|
|
576285
577394
|
|
|
576286
577395
|
const valWrap = document.createElement('div');
|
|
576287
|
-
valWrap.style.cssText = 'flex:1;min-width:0;color
|
|
576288
|
-
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);' });
|
|
576289
577398
|
row.appendChild(valWrap);
|
|
576290
577399
|
|
|
576291
577400
|
argsDiv.appendChild(row);
|
|
@@ -576302,8 +577411,8 @@ function renderToolCallEvent(parent, chunkLike) {
|
|
|
576302
577411
|
function renderToolResultEvent(parent, chunkLike) {
|
|
576303
577412
|
const resultEl = document.createElement('div');
|
|
576304
577413
|
const errStyle = chunkLike.success === false
|
|
576305
|
-
? 'background:#2a1e1e;border-left:2px solid
|
|
576306
|
-
: '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);';
|
|
576307
577416
|
resultEl.style.cssText = errStyle + 'padding:4px 8px 4px 18px;margin:0 0 2px 0;font-size:0.65rem';
|
|
576308
577417
|
appendExpandableContent(resultEl, chunkLike.output || '', { truncateAt: 150, baseStyle: 'color:inherit;' });
|
|
576309
577418
|
parent.appendChild(resultEl);
|
|
@@ -576316,9 +577425,9 @@ function renderToolResultEvent(parent, chunkLike) {
|
|
|
576316
577425
|
// active assistant turn's tool dropdowns.
|
|
576317
577426
|
function renderCheckinEvent(parent, content) {
|
|
576318
577427
|
const el = document.createElement('div');
|
|
576319
|
-
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';
|
|
576320
577429
|
const label = document.createElement('div');
|
|
576321
|
-
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';
|
|
576322
577431
|
label.textContent = '\\u25B8 user check-in';
|
|
576323
577432
|
el.appendChild(label);
|
|
576324
577433
|
const body = document.createElement('div');
|
|
@@ -576335,9 +577444,9 @@ function renderCheckinEvent(parent, content) {
|
|
|
576335
577444
|
// stack of dropdowns.
|
|
576336
577445
|
function renderTriageResponseEvent(parent, ack, steering) {
|
|
576337
577446
|
const el = document.createElement('div');
|
|
576338
|
-
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';
|
|
576339
577448
|
const label = document.createElement('div');
|
|
576340
|
-
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';
|
|
576341
577450
|
label.textContent = '\\u25B8 triage \\u2192 main agent';
|
|
576342
577451
|
el.appendChild(label);
|
|
576343
577452
|
if (ack) {
|
|
@@ -576409,7 +577518,7 @@ async function sendCheckin() {
|
|
|
576409
577518
|
if (!r.ok) {
|
|
576410
577519
|
if (toolsContainer) {
|
|
576411
577520
|
const errEl = document.createElement('div');
|
|
576412
|
-
errEl.style.cssText = 'color
|
|
577521
|
+
errEl.style.cssText = 'color:var(--color-error);font-size:0.6rem;padding:2px 14px';
|
|
576413
577522
|
errEl.textContent = 'Check-in failed: HTTP ' + r.status;
|
|
576414
577523
|
toolsContainer.appendChild(errEl);
|
|
576415
577524
|
}
|
|
@@ -576423,7 +577532,7 @@ async function sendCheckin() {
|
|
|
576423
577532
|
} catch (err) {
|
|
576424
577533
|
if (toolsContainer) {
|
|
576425
577534
|
const errEl = document.createElement('div');
|
|
576426
|
-
errEl.style.cssText = 'color
|
|
577535
|
+
errEl.style.cssText = 'color:var(--color-error);font-size:0.6rem;padding:2px 14px';
|
|
576427
577536
|
errEl.textContent = 'Check-in network error: ' + (err && err.message || String(err));
|
|
576428
577537
|
toolsContainer.appendChild(errEl);
|
|
576429
577538
|
}
|
|
@@ -576466,6 +577575,11 @@ async function sendMessage() {
|
|
|
576466
577575
|
const contentDiv = document.createElement('div');
|
|
576467
577576
|
msgDiv.appendChild(contentDiv);
|
|
576468
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
|
+
|
|
576469
577583
|
try {
|
|
576470
577584
|
// Prepend context files as a FILES block so the agent can read them
|
|
576471
577585
|
// without having to guess paths. The user picked these via right-click
|
|
@@ -576569,9 +577683,9 @@ async function sendMessage() {
|
|
|
576569
577683
|
if (chunk.type === 'tool_call') {
|
|
576570
577684
|
chatTools.push(chunk);
|
|
576571
577685
|
const details = document.createElement('details');
|
|
576572
|
-
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';
|
|
576573
577687
|
const summary = document.createElement('summary');
|
|
576574
|
-
summary.style.cssText = 'padding:4px 8px;color
|
|
577688
|
+
summary.style.cssText = 'padding:4px 8px;color:var(--color-brand);cursor:pointer';
|
|
576575
577689
|
|
|
576576
577690
|
// Build a compact one-line label so the user sees what the tool
|
|
576577
577691
|
// is actually doing without expanding. todo_write gets a special
|
|
@@ -576629,18 +577743,18 @@ async function sendMessage() {
|
|
|
576629
577743
|
// todo_write specifically render a checklist instead of a blob.
|
|
576630
577744
|
if (chunk.args && typeof chunk.args === 'object') {
|
|
576631
577745
|
const argsDiv = document.createElement('div');
|
|
576632
|
-
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)';
|
|
576633
577747
|
if (toolName === 'todo_write' && Array.isArray(a.todos)) {
|
|
576634
577748
|
for (const t of a.todos) {
|
|
576635
577749
|
if (!t || typeof t !== 'object') continue;
|
|
576636
577750
|
const row = document.createElement('div');
|
|
576637
577751
|
row.style.cssText = 'padding:2px 0';
|
|
576638
577752
|
let mark = '○';
|
|
576639
|
-
let color = '
|
|
577753
|
+
let color = 'var(--color-fg-subtle)';
|
|
576640
577754
|
if (t.status === 'completed') { mark = '◉'; color = '#4a7a4a'; }
|
|
576641
|
-
else if (t.status === 'in_progress') { mark = '◐'; color = '
|
|
576642
|
-
else if (t.status === 'blocked') { mark = '◍'; color = '
|
|
576643
|
-
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>' : '');
|
|
576644
577758
|
argsDiv.appendChild(row);
|
|
576645
577759
|
}
|
|
576646
577760
|
} else {
|
|
@@ -576655,15 +577769,15 @@ async function sendMessage() {
|
|
|
576655
577769
|
else { try { vs = JSON.stringify(v, null, 2); } catch { vs = '[object]'; } }
|
|
576656
577770
|
|
|
576657
577771
|
const keyEl = document.createElement('span');
|
|
576658
|
-
keyEl.style.cssText = 'color
|
|
577772
|
+
keyEl.style.cssText = 'color:var(--color-brand);min-width:60px;flex-shrink:0';
|
|
576659
577773
|
keyEl.textContent = k;
|
|
576660
577774
|
row.appendChild(keyEl);
|
|
576661
577775
|
|
|
576662
577776
|
const valWrap = document.createElement('div');
|
|
576663
|
-
valWrap.style.cssText = 'flex:1;min-width:0;color
|
|
577777
|
+
valWrap.style.cssText = 'flex:1;min-width:0;color:var(--color-fg)';
|
|
576664
577778
|
// Use the show-more helper so long values are collapsed
|
|
576665
577779
|
// by default and the user can expand inline.
|
|
576666
|
-
appendExpandableContent(valWrap, vs, { truncateAt: 500, baseStyle: 'color
|
|
577780
|
+
appendExpandableContent(valWrap, vs, { truncateAt: 500, baseStyle: 'color:var(--color-fg);' });
|
|
576667
577781
|
row.appendChild(valWrap);
|
|
576668
577782
|
|
|
576669
577783
|
argsDiv.appendChild(row);
|
|
@@ -576681,8 +577795,8 @@ async function sendMessage() {
|
|
|
576681
577795
|
// 150 chars. The button sits underneath the result block.
|
|
576682
577796
|
if (chunk.type === 'tool_result') {
|
|
576683
577797
|
const resultEl = document.createElement('div');
|
|
576684
|
-
resultEl.style.cssText = 'background
|
|
576685
|
-
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);' });
|
|
576686
577800
|
toolsContainer.appendChild(resultEl);
|
|
576687
577801
|
continue;
|
|
576688
577802
|
}
|
|
@@ -576696,8 +577810,15 @@ async function sendMessage() {
|
|
|
576696
577810
|
// Content delta
|
|
576697
577811
|
const delta = chunk.choices?.[0]?.delta?.content || '';
|
|
576698
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
|
+
}
|
|
576699
577819
|
fullContent += delta;
|
|
576700
577820
|
contentDiv.innerHTML = renderMarkdown(fullContent);
|
|
577821
|
+
try { _highlightCodeBlocks(contentDiv); } catch {}
|
|
576701
577822
|
maybeAutoScroll();
|
|
576702
577823
|
}
|
|
576703
577824
|
} catch {}
|
|
@@ -576719,11 +577840,14 @@ async function sendMessage() {
|
|
|
576719
577840
|
|
|
576720
577841
|
// Final render: content + collapsible tools + metadata
|
|
576721
577842
|
contentDiv.innerHTML = renderMarkdown(fullContent);
|
|
577843
|
+
// OWUI-3: streaming complete — drop the pulsing dot.
|
|
577844
|
+
hideStreamingIndicator(msgDiv);
|
|
577845
|
+
try { _highlightCodeBlocks(contentDiv); } catch {}
|
|
576722
577846
|
|
|
576723
577847
|
// Metadata bar: turns, tokens, duration (always shown, compact)
|
|
576724
577848
|
if (metaInfo) {
|
|
576725
577849
|
const metaBar = document.createElement('div');
|
|
576726
|
-
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';
|
|
576727
577851
|
const parts = [];
|
|
576728
577852
|
if (metaInfo.turns) parts.push(metaInfo.turns + ' turn' + (metaInfo.turns > 1 ? 's' : ''));
|
|
576729
577853
|
if (metaInfo.toolCalls) parts.push(metaInfo.toolCalls + ' tool call' + (metaInfo.toolCalls > 1 ? 's' : ''));
|
|
@@ -576736,9 +577860,9 @@ async function sendMessage() {
|
|
|
576736
577860
|
// Collapse tool calls into a dropdown
|
|
576737
577861
|
if (chatTools.length > 0) {
|
|
576738
577862
|
const details = document.createElement('details');
|
|
576739
|
-
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)';
|
|
576740
577864
|
const summary = document.createElement('summary');
|
|
576741
|
-
summary.style.cssText = 'cursor:pointer;color
|
|
577865
|
+
summary.style.cssText = 'cursor:pointer;color:var(--color-fg-muted);font-size:0.6rem';
|
|
576742
577866
|
summary.textContent = 'show ' + chatTools.length + ' tool call' + (chatTools.length > 1 ? 's' : '');
|
|
576743
577867
|
details.appendChild(summary);
|
|
576744
577868
|
while (toolsContainer.firstChild) details.appendChild(toolsContainer.firstChild);
|
|
@@ -576757,13 +577881,17 @@ async function sendMessage() {
|
|
|
576757
577881
|
// Match the red left-border styling used by failed tool_result
|
|
576758
577882
|
// and the stop-button. Sits inside the assistant bubble so it
|
|
576759
577883
|
// visually parents to the same turn the user initiated.
|
|
576760
|
-
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>';
|
|
576761
577885
|
}
|
|
576762
577886
|
|
|
576763
577887
|
streaming = false;
|
|
576764
577888
|
chatAbortController = null;
|
|
576765
577889
|
document.getElementById('send-btn').style.display = 'inline-block';
|
|
576766
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 {}
|
|
576767
577895
|
maybeAutoScroll();
|
|
576768
577896
|
}
|
|
576769
577897
|
|
|
@@ -576806,9 +577934,13 @@ function switchTab(tab) {
|
|
|
576806
577934
|
panel.style.display = (tab === 'chat' || tab === 'agent') ? 'flex' : 'block';
|
|
576807
577935
|
}
|
|
576808
577936
|
document.getElementById('footer').style.display = tab === 'chat' ? 'flex' : 'none';
|
|
576809
|
-
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)'; });
|
|
576810
577938
|
const active = document.getElementById('tab-' + tab);
|
|
576811
|
-
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
|
+
});
|
|
576812
577944
|
if (tab === 'jobs') loadJobs();
|
|
576813
577945
|
if (tab === 'agent') {
|
|
576814
577946
|
loadProfiles();
|
|
@@ -576833,7 +577965,7 @@ async function loadProjects() {
|
|
|
576833
577965
|
const resp = await fetch('/v1/projects', { headers: headers() });
|
|
576834
577966
|
if (!resp.ok) {
|
|
576835
577967
|
document.getElementById('projects-list').innerHTML =
|
|
576836
|
-
'<div style="color
|
|
577968
|
+
'<div style="color:var(--color-error)">Failed to load projects: HTTP ' + resp.status + '</div>';
|
|
576837
577969
|
return;
|
|
576838
577970
|
}
|
|
576839
577971
|
const data = await resp.json();
|
|
@@ -576857,8 +577989,8 @@ async function loadProjects() {
|
|
|
576857
577989
|
}
|
|
576858
577990
|
if (projects.length === 0) {
|
|
576859
577991
|
document.getElementById('projects-list').innerHTML =
|
|
576860
|
-
'<div style="color
|
|
576861
|
-
'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.' +
|
|
576862
577994
|
'</div>';
|
|
576863
577995
|
return;
|
|
576864
577996
|
}
|
|
@@ -576872,17 +578004,17 @@ async function loadProjects() {
|
|
|
576872
578004
|
};
|
|
576873
578005
|
const isCur = (p) => current && current.root === p.root;
|
|
576874
578006
|
const rows = projects.map(p => {
|
|
576875
|
-
const accent = isCur(p) ? '
|
|
578007
|
+
const accent = isCur(p) ? 'var(--color-brand)' : 'var(--color-border)';
|
|
576876
578008
|
const label = (p.name || p.root.split('/').pop() || p.root)
|
|
576877
578009
|
.replace(/</g, '<').replace(/>/g, '>');
|
|
576878
578010
|
const rootDisplay = p.root.replace(/</g, '<').replace(/>/g, '>');
|
|
576879
|
-
const pidInfo = p.pid ? ' <span style="color
|
|
576880
|
-
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">' +
|
|
576881
578013
|
'<div>' +
|
|
576882
|
-
'<div style="color:' + (isCur(p) ? '
|
|
576883
|
-
'<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>' +
|
|
576884
578016
|
'</div>' +
|
|
576885
|
-
'<div style="color
|
|
578017
|
+
'<div style="color:var(--color-fg-faint);font-size:0.62rem;text-align:right">' +
|
|
576886
578018
|
fmtAgo(p.lastSeen) + pidInfo +
|
|
576887
578019
|
'</div>' +
|
|
576888
578020
|
'</div>';
|
|
@@ -576918,7 +578050,7 @@ async function loadProjects() {
|
|
|
576918
578050
|
});
|
|
576919
578051
|
} catch (e) {
|
|
576920
578052
|
document.getElementById('projects-list').innerHTML =
|
|
576921
|
-
'<div style="color
|
|
578053
|
+
'<div style="color:var(--color-error)">Failed to load projects: ' + (e && e.message ? e.message : String(e)) + '</div>';
|
|
576922
578054
|
}
|
|
576923
578055
|
}
|
|
576924
578056
|
window.loadProjects = loadProjects;
|
|
@@ -576939,8 +578071,8 @@ async function loadConfig() {
|
|
|
576939
578071
|
document.getElementById('config-content').innerHTML =
|
|
576940
578072
|
'<table style="width:100%">' +
|
|
576941
578073
|
Object.entries(c).map(([k,v]) =>
|
|
576942
|
-
'<tr style="border-bottom:1px solid
|
|
576943
|
-
'<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>'
|
|
576944
578076
|
).join('') + '</table>';
|
|
576945
578077
|
document.getElementById('config-endpoint').textContent = ep.url + ' (' + (ep.backendType || 'unknown') + ')';
|
|
576946
578078
|
// Populate model switcher
|
|
@@ -576959,11 +578091,11 @@ async function loadConfig() {
|
|
|
576959
578091
|
const r = await fetch('/v1/profiles', { headers: headers() });
|
|
576960
578092
|
const d = await r.json();
|
|
576961
578093
|
document.getElementById('config-profiles').innerHTML = (d.profiles || []).map(p =>
|
|
576962
|
-
'<div style="background
|
|
576963
|
-
'<span style="color
|
|
576964
|
-
(p.encrypted ? ' <span style="color
|
|
576965
|
-
' <span style="color
|
|
576966
|
-
).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>';
|
|
576967
578099
|
} catch {}
|
|
576968
578100
|
}
|
|
576969
578101
|
|
|
@@ -577016,17 +578148,17 @@ async function loadActivity() {
|
|
|
577016
578148
|
const r = await fetch('/v1/audit?limit=50', { headers: headers() });
|
|
577017
578149
|
const d = await r.json();
|
|
577018
578150
|
const feed = document.getElementById('activity-feed');
|
|
577019
|
-
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; }
|
|
577020
578152
|
feed.innerHTML = d.records.map(r => {
|
|
577021
578153
|
const time = r.ts?.split('T')[1]?.slice(0,8) || '';
|
|
577022
|
-
const color = r.status >= 400 ? '
|
|
577023
|
-
return '<div style="padding:3px 0;border-bottom:1px solid
|
|
577024
|
-
'<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> ' +
|
|
577025
578157
|
'<span style="color:' + color + '">' + r.status + '</span> ' +
|
|
577026
|
-
'<span style="color
|
|
577027
|
-
'<span style="color
|
|
577028
|
-
'<span style="color
|
|
577029
|
-
'<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>';
|
|
577030
578162
|
}).join('');
|
|
577031
578163
|
} catch {}
|
|
577032
578164
|
}
|
|
@@ -577040,15 +578172,15 @@ async function loadDaemons() {
|
|
|
577040
578172
|
const d = await r.json();
|
|
577041
578173
|
const el = document.getElementById('dashboard-daemons');
|
|
577042
578174
|
if (!d.runs?.length) {
|
|
577043
|
-
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>';
|
|
577044
578176
|
return;
|
|
577045
578177
|
}
|
|
577046
|
-
el.innerHTML = '<h3 style="color
|
|
578178
|
+
el.innerHTML = '<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:8px">Active Processes</h3>' +
|
|
577047
578179
|
d.runs.map(j =>
|
|
577048
|
-
'<div style="background
|
|
577049
|
-
'<span style="color
|
|
577050
|
-
'<span style="color
|
|
577051
|
-
'<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>'
|
|
577052
578184
|
).join('');
|
|
577053
578185
|
} catch {}
|
|
577054
578186
|
}
|
|
@@ -577062,33 +578194,33 @@ async function loadScheduled() {
|
|
|
577062
578194
|
if (!el) return;
|
|
577063
578195
|
const tasks = Array.isArray(d.tasks) ? d.tasks : [];
|
|
577064
578196
|
if (tasks.length === 0) {
|
|
577065
|
-
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>';
|
|
577066
578198
|
return;
|
|
577067
578199
|
}
|
|
577068
578200
|
const rows = tasks.map(t => {
|
|
577069
578201
|
const enabled = !!t.enabled;
|
|
577070
578202
|
const btn = enabled
|
|
577071
|
-
? '<button onclick="toggleScheduled(\\'' + t.id + '\\',false)" style="background
|
|
577072
|
-
: '<button onclick="toggleScheduled(\\'' + t.id + '\\',true)" style="background
|
|
577073
|
-
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)';
|
|
577074
578206
|
const procInfo = t.procs && t.procs.length ? (' (' + t.procs.length + ' proc)') : '';
|
|
577075
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') : '';
|
|
577076
|
-
const killBtn = '<button onclick="killScheduledTask(\\'' + t.id + '\\')" style="background
|
|
577077
|
-
const row = '<div style="background
|
|
577078
|
-
+ '<div style="color
|
|
577079
|
-
+ '<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>'
|
|
577080
578212
|
+ '<div style="margin-top:4px;display:flex;gap:8px">' + btn + killBtn + '</div>'
|
|
577081
578213
|
+ '</div>';
|
|
577082
578214
|
return row;
|
|
577083
578215
|
}).join('');
|
|
577084
|
-
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
|
|
577085
578217
|
+ '<div style="margin-top:6px;display:flex;gap:8px">'
|
|
577086
|
-
+ '<button onclick="disableAllScheduled()" title="Disable all scheduled tasks" style="background
|
|
577087
|
-
+ '<button onclick="enableAllScheduled()" title="Enable all scheduled tasks" style="background
|
|
577088
|
-
+ '<button onclick="killScheduled()" title="Kill OA scheduler processes" style="background
|
|
577089
|
-
+ '<button onclick="adoptScheduled()" title="Adopt legacy cron jobs into tasks.json" style="background
|
|
577090
|
-
+ '<button onclick="fixupScheduled()" title="Rewrite cron entries to canonical form" style="background
|
|
577091
|
-
+ '<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>'
|
|
577092
578224
|
+ '</div>';
|
|
577093
578225
|
} catch {}
|
|
577094
578226
|
}
|
|
@@ -577235,15 +578367,15 @@ async function loadServices() {
|
|
|
577235
578367
|
const svcs = Array.isArray(d.services) ? d.services : [];
|
|
577236
578368
|
if (!svcs.length) { el.innerHTML = ''; return; }
|
|
577237
578369
|
const rows = svcs.map(s => {
|
|
577238
|
-
const stopBtn = '<button onclick="svcAction(\\'' + s.name + '\\',\\'stop\\')" style="background
|
|
577239
|
-
const disBtn = '<button onclick="svcAction(\\'' + s.name + '\\',\\'disable\\')" style="background
|
|
577240
|
-
return '<div style="background
|
|
577241
|
-
+ '<div style="color
|
|
577242
|
-
+ '<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>'
|
|
577243
578375
|
+ '<div style="margin-top:4px;display:flex;gap:8px">' + stopBtn + disBtn + '</div>'
|
|
577244
578376
|
+ '</div>';
|
|
577245
578377
|
}).join('');
|
|
577246
|
-
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;
|
|
577247
578379
|
} catch {}
|
|
577248
578380
|
}
|
|
577249
578381
|
|
|
@@ -577335,7 +578467,7 @@ async function submitAgentTask() {
|
|
|
577335
578467
|
div.style.padding = '2px 0';
|
|
577336
578468
|
if (evt.type === 'run_started') {
|
|
577337
578469
|
currentRunId = evt.run_id;
|
|
577338
|
-
div.innerHTML = '<span style="color
|
|
578470
|
+
div.innerHTML = '<span style="color:var(--color-brand)">Task started</span> — run_id: ' + evt.run_id;
|
|
577339
578471
|
// WO-TASK-02: Persist this run into the agent runs storage so
|
|
577340
578472
|
// it appears in the #agent-session-select dropdown immediately.
|
|
577341
578473
|
try {
|
|
@@ -577351,12 +578483,12 @@ async function submitAgentTask() {
|
|
|
577351
578483
|
currentAgentRunId = evt.run_id;
|
|
577352
578484
|
} catch {}
|
|
577353
578485
|
} else if (evt.type === 'run_completed') {
|
|
577354
|
-
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;
|
|
577355
578487
|
document.getElementById('agent-submit').style.display = 'inline-block';
|
|
577356
578488
|
document.getElementById('agent-abort').style.display = 'none';
|
|
577357
578489
|
currentRunId = null;
|
|
577358
578490
|
} else if (evt.type === 'stdout') {
|
|
577359
|
-
div.style.color = '
|
|
578491
|
+
div.style.color = 'var(--color-fg-muted)';
|
|
577360
578492
|
div.style.fontFamily = 'inherit';
|
|
577361
578493
|
div.textContent = evt.data?.slice?.(0, 200) || '';
|
|
577362
578494
|
}
|
|
@@ -577366,7 +578498,7 @@ async function submitAgentTask() {
|
|
|
577366
578498
|
}
|
|
577367
578499
|
}
|
|
577368
578500
|
} catch (err) {
|
|
577369
|
-
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>';
|
|
577370
578502
|
}
|
|
577371
578503
|
document.getElementById('agent-submit').style.display = 'inline-block';
|
|
577372
578504
|
document.getElementById('agent-abort').style.display = 'none';
|
|
@@ -577383,15 +578515,15 @@ async function loadDashboard() {
|
|
|
577383
578515
|
const r = await fetch('/health', { headers: headers() });
|
|
577384
578516
|
const d = await r.json();
|
|
577385
578517
|
document.getElementById('dashboard-health').innerHTML =
|
|
577386
|
-
'<div style="background
|
|
577387
|
-
'<div style="color
|
|
577388
|
-
'<div style="color
|
|
577389
|
-
'<div style="background
|
|
577390
|
-
'<div style="color
|
|
577391
|
-
'<div style="color
|
|
577392
|
-
'<div style="background
|
|
577393
|
-
'<div style="color
|
|
577394
|
-
'<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>';
|
|
577395
578527
|
} catch {}
|
|
577396
578528
|
// System info + model recommendations
|
|
577397
578529
|
try {
|
|
@@ -577400,15 +578532,15 @@ async function loadDashboard() {
|
|
|
577400
578532
|
const gpuHtml = (sys.gpu || []).map(g => g.name + ' (' + g.vram_gb + 'GB)').join(', ') || 'No GPU detected';
|
|
577401
578533
|
const healthEl = document.getElementById('dashboard-health');
|
|
577402
578534
|
healthEl.innerHTML +=
|
|
577403
|
-
'<div style="background
|
|
577404
|
-
'<div style="color
|
|
577405
|
-
'<div style="color
|
|
577406
|
-
'<div style="background
|
|
577407
|
-
'<div style="color
|
|
577408
|
-
'<div style="color
|
|
577409
|
-
'<div style="background
|
|
577410
|
-
'<div style="color
|
|
577411
|
-
'<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>';
|
|
577412
578544
|
// Store for model badges
|
|
577413
578545
|
window._sysMaxParams = sys.recommended_max_params;
|
|
577414
578546
|
} catch {}
|
|
@@ -577421,30 +578553,30 @@ async function loadDashboard() {
|
|
|
577421
578553
|
for (const [label, stats] of Object.entries(ps)) {
|
|
577422
578554
|
const s = stats;
|
|
577423
578555
|
providerCards +=
|
|
577424
|
-
'<div style="background
|
|
577425
|
-
'<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>' +
|
|
577426
578558
|
'<div style="display:flex;gap:16px;margin-top:4px">' +
|
|
577427
|
-
'<span style="color
|
|
577428
|
-
'<span style="color
|
|
577429
|
-
'<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>' +
|
|
577430
578562
|
'</div></div>';
|
|
577431
578563
|
}
|
|
577432
578564
|
const totalIn = d.persistent?.totalIn || d.totalTokensIn || 0;
|
|
577433
578565
|
const totalOut = d.persistent?.totalOut || d.totalTokensOut || 0;
|
|
577434
578566
|
document.getElementById('dashboard-usage').innerHTML =
|
|
577435
|
-
'<h3 style="color
|
|
578567
|
+
'<h3 style="color:var(--color-brand);font-size:0.7rem;margin-bottom:8px">Token Usage by Provider (persistent)</h3>' +
|
|
577436
578568
|
'<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:8px">' +
|
|
577437
|
-
'<div style="background
|
|
577438
|
-
'<div style="color
|
|
577439
|
-
'<div style="color
|
|
577440
|
-
'<div style="background
|
|
577441
|
-
'<div style="color
|
|
577442
|
-
'<div style="color
|
|
577443
|
-
'<div style="background
|
|
577444
|
-
'<div style="color
|
|
577445
|
-
'<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>' +
|
|
577446
578578
|
'</div>' +
|
|
577447
|
-
(providerCards || '<div style="color
|
|
578579
|
+
(providerCards || '<div style="color:var(--color-fg-faint);font-size:0.7rem">No provider usage recorded yet</div>');
|
|
577448
578580
|
} catch {}
|
|
577449
578581
|
}
|
|
577450
578582
|
|
|
@@ -577455,20 +578587,20 @@ async function loadJobs() {
|
|
|
577455
578587
|
try {
|
|
577456
578588
|
const r = await fetch('/v1/runs', { headers: headers() });
|
|
577457
578589
|
const d = await r.json();
|
|
577458
|
-
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; }
|
|
577459
578591
|
let html = '<table style="width:100%;border-collapse:collapse">';
|
|
577460
|
-
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>';
|
|
577461
578593
|
for (const j of d.runs.slice(0, 20)) {
|
|
577462
|
-
const color = j.status === 'completed' ? '
|
|
578594
|
+
const color = j.status === 'completed' ? 'var(--color-success)' : j.status === 'running' ? 'var(--color-brand)' : 'var(--color-error)';
|
|
577463
578595
|
const dur = j.durationMs ? (j.durationMs / 1000).toFixed(1) + 's' : '—';
|
|
577464
|
-
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>';
|
|
577465
578597
|
html += '<td style="color:' + color + '">' + (j.status||'?') + '</td>';
|
|
577466
|
-
html += '<td style="color
|
|
577467
|
-
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>';
|
|
577468
578600
|
}
|
|
577469
578601
|
html += '</table>';
|
|
577470
578602
|
list.innerHTML = html;
|
|
577471
|
-
} catch { list.innerHTML = '<div style="color
|
|
578603
|
+
} catch { list.innerHTML = '<div style="color:var(--color-error)">Failed to load jobs</div>'; }
|
|
577472
578604
|
}
|
|
577473
578605
|
|
|
577474
578606
|
// Session storage (localStorage for persistence across page reloads)
|
|
@@ -577695,7 +578827,7 @@ function switchSession(id) {
|
|
|
577695
578827
|
// Restore metadata bar
|
|
577696
578828
|
if (m.meta && m.role === 'assistant') {
|
|
577697
578829
|
const metaBar = document.createElement('div');
|
|
577698
|
-
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';
|
|
577699
578831
|
const parts = [];
|
|
577700
578832
|
if (m.meta.turns) parts.push(m.meta.turns + ' turns');
|
|
577701
578833
|
if (m.meta.toolCalls) parts.push(m.meta.toolCalls + ' tool calls');
|
|
@@ -577706,24 +578838,24 @@ function switchSession(id) {
|
|
|
577706
578838
|
// Restore tool call provenance with expandable args
|
|
577707
578839
|
if (m.tools?.length && m.role === 'assistant') {
|
|
577708
578840
|
const outerDetails = document.createElement('details');
|
|
577709
|
-
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)';
|
|
577710
578842
|
const outerSummary = document.createElement('summary');
|
|
577711
|
-
outerSummary.style.cssText = 'cursor:pointer;color
|
|
578843
|
+
outerSummary.style.cssText = 'cursor:pointer;color:var(--color-fg-muted)';
|
|
577712
578844
|
outerSummary.textContent = 'show ' + m.tools.length + ' tool calls';
|
|
577713
578845
|
outerDetails.appendChild(outerSummary);
|
|
577714
578846
|
for (const t of m.tools) {
|
|
577715
578847
|
const toolObj = typeof t === 'string' ? { tool: t } : t;
|
|
577716
578848
|
const td = document.createElement('details');
|
|
577717
|
-
td.style.cssText = 'background
|
|
578849
|
+
td.style.cssText = 'background:var(--color-bg-elevated);border-left:2px solid var(--color-brand);margin:2px 0';
|
|
577718
578850
|
const ts = document.createElement('summary');
|
|
577719
|
-
ts.style.cssText = 'padding:4px 8px;color
|
|
578851
|
+
ts.style.cssText = 'padding:4px 8px;color:var(--color-brand);cursor:pointer;font-size:0.65rem';
|
|
577720
578852
|
ts.textContent = toolObj.tool || String(t);
|
|
577721
578853
|
td.appendChild(ts);
|
|
577722
578854
|
if (toolObj.args) {
|
|
577723
578855
|
const ad = document.createElement('div');
|
|
577724
|
-
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';
|
|
577725
578857
|
for (const [k, v] of Object.entries(toolObj.args)) {
|
|
577726
|
-
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>';
|
|
577727
578859
|
}
|
|
577728
578860
|
td.appendChild(ad);
|
|
577729
578861
|
}
|
|
@@ -577809,13 +578941,13 @@ async function loadAgentWorkspaceRoot() {
|
|
|
577809
578941
|
const rootPath = d.path || '.';
|
|
577810
578942
|
cwdEl.innerHTML = '';
|
|
577811
578943
|
const wsSpan = document.createElement('span');
|
|
577812
|
-
wsSpan.style.cssText = 'color
|
|
578944
|
+
wsSpan.style.cssText = 'color:var(--color-fg-muted)';
|
|
577813
578945
|
wsSpan.textContent = 'ROOT: ' + rootPath;
|
|
577814
578946
|
cwdEl.appendChild(wsSpan);
|
|
577815
578947
|
cwdEl.appendChild(document.createElement('br'));
|
|
577816
578948
|
const wdSpan = document.createElement('span');
|
|
577817
578949
|
wdSpan.id = 'agent-workspace-dir-label';
|
|
577818
|
-
wdSpan.style.cssText = 'color
|
|
578950
|
+
wdSpan.style.cssText = 'color:var(--color-brand)';
|
|
577819
578951
|
wdSpan.textContent = 'CWD: ' + (agentWorkingDir || rootPath);
|
|
577820
578952
|
cwdEl.appendChild(wdSpan);
|
|
577821
578953
|
|
|
@@ -577825,7 +578957,7 @@ async function loadAgentWorkspaceRoot() {
|
|
|
577825
578957
|
renderAgentWorkspaceTree();
|
|
577826
578958
|
} catch {
|
|
577827
578959
|
const tree = document.getElementById('agent-workspace-tree');
|
|
577828
|
-
if (tree) tree.innerHTML = '<div style="color
|
|
578960
|
+
if (tree) tree.innerHTML = '<div style="color:var(--color-fg-faint)">Could not load files</div>';
|
|
577829
578961
|
}
|
|
577830
578962
|
}
|
|
577831
578963
|
|
|
@@ -577856,7 +578988,7 @@ function renderAgentTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
577856
578988
|
|
|
577857
578989
|
if (e.type === 'dir') {
|
|
577858
578990
|
const caret = document.createElement('span');
|
|
577859
|
-
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';
|
|
577860
578992
|
caret.textContent = agentTreeExpanded.has(childAbs) ? '▾' : '▸';
|
|
577861
578993
|
row.appendChild(caret);
|
|
577862
578994
|
const icon = document.createElement('span');
|
|
@@ -577864,7 +578996,7 @@ function renderAgentTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
577864
578996
|
row.appendChild(icon);
|
|
577865
578997
|
const name = document.createElement('span');
|
|
577866
578998
|
name.textContent = e.name;
|
|
577867
|
-
name.style.cssText = 'color
|
|
578999
|
+
name.style.cssText = 'color:var(--color-brand)';
|
|
577868
579000
|
if (agentWorkingDir && childAbs === agentWorkingDir) {
|
|
577869
579001
|
name.style.cssText += ';font-weight:bold;text-decoration:underline';
|
|
577870
579002
|
}
|
|
@@ -577908,8 +579040,8 @@ function renderAgentTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
577908
579040
|
const name = document.createElement('span');
|
|
577909
579041
|
name.textContent = e.name;
|
|
577910
579042
|
name.style.cssText = agentContextFiles.includes(childAbs)
|
|
577911
|
-
? 'color
|
|
577912
|
-
: 'color
|
|
579043
|
+
? 'color:var(--color-brand);font-weight:bold'
|
|
579044
|
+
: 'color:var(--color-fg)';
|
|
577913
579045
|
row.appendChild(name);
|
|
577914
579046
|
// Click toggles file in/out of agent context
|
|
577915
579047
|
row.addEventListener('click', () => {
|
|
@@ -577948,13 +579080,13 @@ async function loadWorkspaceRoot() {
|
|
|
577948
579080
|
const rootPath = d.path || '.';
|
|
577949
579081
|
cwdEl.innerHTML = '';
|
|
577950
579082
|
const wsSpan = document.createElement('span');
|
|
577951
|
-
wsSpan.style.cssText = 'color
|
|
579083
|
+
wsSpan.style.cssText = 'color:var(--color-fg-muted)';
|
|
577952
579084
|
wsSpan.textContent = 'ROOT: ' + rootPath;
|
|
577953
579085
|
cwdEl.appendChild(wsSpan);
|
|
577954
579086
|
cwdEl.appendChild(document.createElement('br'));
|
|
577955
579087
|
const wdSpan = document.createElement('span');
|
|
577956
579088
|
wdSpan.id = 'workspace-dir-label';
|
|
577957
|
-
wdSpan.style.cssText = 'color
|
|
579089
|
+
wdSpan.style.cssText = 'color:var(--color-brand)';
|
|
577958
579090
|
wdSpan.textContent = 'CWD: ' + (chatWorkingDir || rootPath);
|
|
577959
579091
|
cwdEl.appendChild(wdSpan);
|
|
577960
579092
|
|
|
@@ -577965,7 +579097,7 @@ async function loadWorkspaceRoot() {
|
|
|
577965
579097
|
treeRoot = rootPath;
|
|
577966
579098
|
renderWorkspaceTree();
|
|
577967
579099
|
} catch {
|
|
577968
|
-
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>';
|
|
577969
579101
|
}
|
|
577970
579102
|
}
|
|
577971
579103
|
|
|
@@ -578001,7 +579133,7 @@ function renderTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
578001
579133
|
if (e.type === 'dir') {
|
|
578002
579134
|
const caret = document.createElement('span');
|
|
578003
579135
|
caret.className = 'tree-caret';
|
|
578004
|
-
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';
|
|
578005
579137
|
caret.textContent = treeExpanded.has(childAbs) ? '▾' : '▸';
|
|
578006
579138
|
row.appendChild(caret);
|
|
578007
579139
|
const icon = document.createElement('span');
|
|
@@ -578009,7 +579141,7 @@ function renderTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
578009
579141
|
row.appendChild(icon);
|
|
578010
579142
|
const name = document.createElement('span');
|
|
578011
579143
|
name.textContent = e.name;
|
|
578012
|
-
name.style.cssText = 'color
|
|
579144
|
+
name.style.cssText = 'color:var(--color-brand)';
|
|
578013
579145
|
// Highlight the current workspace
|
|
578014
579146
|
if (chatWorkingDir && childAbs === chatWorkingDir) {
|
|
578015
579147
|
name.style.cssText += ';font-weight:bold;text-decoration:underline';
|
|
@@ -578048,7 +579180,7 @@ function renderTreeNode(parentEl, absPath, depth, isRoot) {
|
|
|
578048
579180
|
name.textContent = e.name;
|
|
578049
579181
|
name.style.cssText = contextFiles.includes(childAbs)
|
|
578050
579182
|
? 'color:#4e94c9;font-weight:bold'
|
|
578051
|
-
: 'color
|
|
579183
|
+
: 'color:var(--color-fg)';
|
|
578052
579184
|
row.appendChild(name);
|
|
578053
579185
|
row.addEventListener('click', (ev) => {
|
|
578054
579186
|
ev.stopPropagation();
|
|
@@ -578079,7 +579211,7 @@ function showTreeContextMenu(x, y, path, type) {
|
|
|
578079
579211
|
if (!treeMenuEl) {
|
|
578080
579212
|
treeMenuEl = document.createElement('div');
|
|
578081
579213
|
treeMenuEl.id = 'tree-menu';
|
|
578082
|
-
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';
|
|
578083
579215
|
document.body.appendChild(treeMenuEl);
|
|
578084
579216
|
document.addEventListener('click', () => { if (treeMenuEl) treeMenuEl.style.display = 'none'; });
|
|
578085
579217
|
}
|
|
@@ -578099,9 +579231,9 @@ function showTreeContextMenu(x, y, path, type) {
|
|
|
578099
579231
|
for (const it of items) {
|
|
578100
579232
|
if (it.show === false) continue;
|
|
578101
579233
|
const row = document.createElement('div');
|
|
578102
|
-
row.style.cssText = 'padding:6px 16px; cursor:pointer; color
|
|
579234
|
+
row.style.cssText = 'padding:6px 16px; cursor:pointer; color:var(--color-fg)';
|
|
578103
579235
|
row.textContent = it.label;
|
|
578104
|
-
row.addEventListener('mouseenter', () => row.style.background = '
|
|
579236
|
+
row.addEventListener('mouseenter', () => row.style.background = 'var(--color-bg-input)');
|
|
578105
579237
|
row.addEventListener('mouseleave', () => row.style.background = '');
|
|
578106
579238
|
row.addEventListener('click', (ev) => {
|
|
578107
579239
|
ev.stopPropagation();
|
|
@@ -578126,7 +579258,7 @@ function setChatWorkingDir(path) {
|
|
|
578126
579258
|
if (s) {
|
|
578127
579259
|
const old = s.textContent;
|
|
578128
579260
|
s.textContent = path ? 'workspace → ' + path.split('/').slice(-2).join('/') : 'workspace cleared';
|
|
578129
|
-
s.style.color = '
|
|
579261
|
+
s.style.color = 'var(--color-success)';
|
|
578130
579262
|
setTimeout(() => { s.textContent = old; s.style.color = ''; }, 3000);
|
|
578131
579263
|
}
|
|
578132
579264
|
// Also push to the Agent tab input if visible
|
|
@@ -578193,18 +579325,18 @@ async function previewFile(path) {
|
|
|
578193
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';
|
|
578194
579326
|
modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
|
|
578195
579327
|
const card = document.createElement('div');
|
|
578196
|
-
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';
|
|
578197
579329
|
const title = document.createElement('div');
|
|
578198
|
-
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';
|
|
578199
579331
|
title.innerHTML = '<span>' + escHtml(path) + '</span>';
|
|
578200
579332
|
const closeBtn = document.createElement('button');
|
|
578201
579333
|
closeBtn.textContent = '×';
|
|
578202
|
-
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';
|
|
578203
579335
|
closeBtn.onclick = () => modal.remove();
|
|
578204
579336
|
title.appendChild(closeBtn);
|
|
578205
579337
|
card.appendChild(title);
|
|
578206
579338
|
const pre = document.createElement('pre');
|
|
578207
|
-
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)';
|
|
578208
579340
|
pre.textContent = d.content || '(empty)';
|
|
578209
579341
|
card.appendChild(pre);
|
|
578210
579342
|
modal.appendChild(card);
|
|
@@ -578224,7 +579356,7 @@ function toggleSandbox() {
|
|
|
578224
579356
|
const btn = document.getElementById('sandbox-toggle');
|
|
578225
579357
|
btn.textContent = 'sandbox: ' + (sandboxMode === 'none' ? 'off' : 'docker');
|
|
578226
579358
|
btn.style.opacity = sandboxMode === 'none' ? '0.5' : '1';
|
|
578227
|
-
btn.style.borderColor = sandboxMode === 'none' ? '
|
|
579359
|
+
btn.style.borderColor = sandboxMode === 'none' ? 'var(--color-border)' : 'var(--color-brand)';
|
|
578228
579360
|
}
|
|
578229
579361
|
|
|
578230
579362
|
// Live system metrics polling
|
|
@@ -578388,8 +579520,8 @@ async function doUpdate() {
|
|
|
578388
579520
|
|
|
578389
579521
|
btn.textContent = 'updated v' + newVersion;
|
|
578390
579522
|
btn.style.background = '#1a3a1a';
|
|
578391
|
-
btn.style.borderColor = '
|
|
578392
|
-
btn.style.color = '
|
|
579523
|
+
btn.style.borderColor = 'var(--color-success)';
|
|
579524
|
+
btn.style.color = 'var(--color-success)';
|
|
578393
579525
|
try { seenVersion = newVersion || seenVersion; } catch {}
|
|
578394
579526
|
|
|
578395
579527
|
// Flash status bar
|
|
@@ -578397,7 +579529,7 @@ async function doUpdate() {
|
|
|
578397
579529
|
if (statusEl) {
|
|
578398
579530
|
const origStatusColor = statusEl.style.color;
|
|
578399
579531
|
statusEl.textContent = 'updated → v' + newVersion;
|
|
578400
|
-
statusEl.style.color = '
|
|
579532
|
+
statusEl.style.color = 'var(--color-success)';
|
|
578401
579533
|
setTimeout(() => { statusEl.style.color = origStatusColor; }, 5000);
|
|
578402
579534
|
}
|
|
578403
579535
|
|
|
@@ -578607,7 +579739,7 @@ function renderInlineError(message) {
|
|
|
578607
579739
|
}
|
|
578608
579740
|
const wrap = document.createElement('div');
|
|
578609
579741
|
wrap.className = 'msg assistant';
|
|
578610
|
-
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>';
|
|
578611
579743
|
conv.appendChild(wrap);
|
|
578612
579744
|
maybeAutoScroll();
|
|
578613
579745
|
}
|
|
@@ -578703,7 +579835,7 @@ function renderTasksRow(todos) {
|
|
|
578703
579835
|
const total = todos.length;
|
|
578704
579836
|
const counter = document.createElement('span');
|
|
578705
579837
|
counter.className = 'tasks-label';
|
|
578706
|
-
counter.style.color = completed === total ? '#5fa55f' : '
|
|
579838
|
+
counter.style.color = completed === total ? '#5fa55f' : 'var(--color-brand)';
|
|
578707
579839
|
counter.textContent = completed + '/' + total;
|
|
578708
579840
|
tasksRowEl.appendChild(counter);
|
|
578709
579841
|
// One pill per task — color-coded by status
|
|
@@ -578751,13 +579883,13 @@ async function refreshTodos(sessionId) {
|
|
|
578751
579883
|
const li = document.createElement('li');
|
|
578752
579884
|
li.style.cssText = 'padding:2px 0;display:flex;gap:8px;align-items:flex-start';
|
|
578753
579885
|
const mark = document.createElement('span');
|
|
578754
|
-
mark.style.cssText = 'color
|
|
579886
|
+
mark.style.cssText = 'color:var(--color-brand);font-family:monospace;flex-shrink:0';
|
|
578755
579887
|
mark.textContent = statusMark(t.status);
|
|
578756
579888
|
li.appendChild(mark);
|
|
578757
579889
|
const content = document.createElement('span');
|
|
578758
579890
|
content.style.cssText = t.status === 'completed'
|
|
578759
|
-
? 'color
|
|
578760
|
-
: 'color
|
|
579891
|
+
? 'color:var(--color-fg-subtle);text-decoration:line-through'
|
|
579892
|
+
: 'color:var(--color-fg)';
|
|
578761
579893
|
content.textContent = t.content + (t.blocker ? ' (blocked: ' + t.blocker + ')' : '');
|
|
578762
579894
|
li.appendChild(content);
|
|
578763
579895
|
todoListEl.appendChild(li);
|
|
@@ -579044,9 +580176,9 @@ async function refreshVoiceState() {
|
|
|
579044
580176
|
const pill = document.getElementById('voice-state-pill');
|
|
579045
580177
|
if (pill) {
|
|
579046
580178
|
pill.textContent = data.state;
|
|
579047
|
-
pill.style.borderLeftColor = data.state === 'listening' ? '
|
|
579048
|
-
: data.state === 'speaking' ? '
|
|
579049
|
-
: 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)';
|
|
579050
580182
|
}
|
|
579051
580183
|
} catch {}
|
|
579052
580184
|
}
|
|
@@ -579088,33 +580220,33 @@ async function loadCloneRefs() {
|
|
|
579088
580220
|
if (!list) return;
|
|
579089
580221
|
try {
|
|
579090
580222
|
const r = await fetch('/v1/voice/clone-refs', { headers: headers() });
|
|
579091
|
-
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; }
|
|
579092
580224
|
const data = await r.json();
|
|
579093
580225
|
const refs = data.refs || [];
|
|
579094
580226
|
if (refs.length === 0) {
|
|
579095
|
-
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>';
|
|
579096
580228
|
return;
|
|
579097
580229
|
}
|
|
579098
580230
|
list.innerHTML = refs.map(ref => {
|
|
579099
|
-
const accent = ref.isActive ? '
|
|
579100
|
-
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>' : '';
|
|
579101
580233
|
const sizeKb = (ref.size / 1024).toFixed(1);
|
|
579102
580234
|
const fnAttr = escAttr(ref.filename);
|
|
579103
580235
|
const nameAttr = escAttr(ref.name);
|
|
579104
|
-
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">' +
|
|
579105
580237
|
'<div>' +
|
|
579106
|
-
'<div style="color:' + (ref.isActive ? '
|
|
579107
|
-
'<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>' +
|
|
579108
580240
|
'</div>' +
|
|
579109
580241
|
'<div style="display:flex;gap:6px">' +
|
|
579110
|
-
(ref.isActive ? '' : '<button onclick="activateCloneRef('' + fnAttr + '')" style="background
|
|
579111
|
-
'<button onclick="renameCloneRef('' + fnAttr + ''
|
|
579112
|
-
'<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>' +
|
|
579113
580245
|
'</div>' +
|
|
579114
580246
|
'</div>';
|
|
579115
580247
|
}).join('');
|
|
579116
580248
|
} catch (e) {
|
|
579117
|
-
list.innerHTML = '<div style="color
|
|
580249
|
+
list.innerHTML = '<div style="color:var(--color-error)">failed: ' + (e?.message || e) + '</div>';
|
|
579118
580250
|
}
|
|
579119
580251
|
}
|
|
579120
580252
|
|
|
@@ -579126,7 +580258,7 @@ async function uploadCloneRef(files) {
|
|
|
579126
580258
|
if (file.size < 4096) { alert('Audio file too small (min ~4 KB)'); return; }
|
|
579127
580259
|
if (file.size > 50 * 1024 * 1024) { alert('Audio file too large (max 50 MB)'); return; }
|
|
579128
580260
|
const list = document.getElementById('clone-refs-list');
|
|
579129
|
-
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>';
|
|
579130
580262
|
try {
|
|
579131
580263
|
const buf = await file.arrayBuffer();
|
|
579132
580264
|
let binary = '';
|
|
@@ -579222,7 +580354,7 @@ async function startVoiceChat() {
|
|
|
579222
580354
|
}
|
|
579223
580355
|
if (_voiceAudioCtx.state === 'suspended') await _voiceAudioCtx.resume();
|
|
579224
580356
|
$voiceConnected.set(true);
|
|
579225
|
-
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; }
|
|
579226
580358
|
document.getElementById('voice-mic-pill').style.display = 'inline-block';
|
|
579227
580359
|
document.getElementById('voice-transcript-pane').style.display = 'block';
|
|
579228
580360
|
addVoiceTranscriptLine('system', 'connected — speak naturally');
|
|
@@ -579240,184 +580372,1239 @@ async function stopVoiceChat(silent) {
|
|
|
579240
580372
|
try { if (_voiceWs) _voiceWs.close(); _voiceWs = null; } catch {}
|
|
579241
580373
|
$voiceConnected.set(false);
|
|
579242
580374
|
const btn = document.getElementById('voice-toggle-btn');
|
|
579243
|
-
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; }
|
|
579244
580376
|
const micPill = document.getElementById('voice-mic-pill');
|
|
579245
580377
|
if (micPill) micPill.style.display = 'none';
|
|
579246
580378
|
if (!silent) addVoiceTranscriptLine('system', 'disconnected');
|
|
579247
580379
|
}
|
|
579248
580380
|
|
|
579249
|
-
async function startMicCapture() {
|
|
579250
|
-
if (!navigator.mediaDevices?.getUserMedia) {
|
|
579251
|
-
throw new Error('mic API not available — needs HTTPS or localhost');
|
|
580381
|
+
async function startMicCapture() {
|
|
580382
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
580383
|
+
throw new Error('mic API not available — needs HTTPS or localhost');
|
|
580384
|
+
}
|
|
580385
|
+
_voiceMicStream = await navigator.mediaDevices.getUserMedia({
|
|
580386
|
+
audio: { channelCount: 1, sampleRate: 16000, echoCancellation: true, noiseSuppression: true },
|
|
580387
|
+
});
|
|
580388
|
+
if (!_voiceAudioCtx || _voiceAudioCtx.state === 'closed') {
|
|
580389
|
+
_voiceAudioCtx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
|
|
580390
|
+
}
|
|
580391
|
+
const src = _voiceAudioCtx.createMediaStreamSource(_voiceMicStream);
|
|
580392
|
+
const proc = _voiceAudioCtx.createScriptProcessor(4096, 1, 1);
|
|
580393
|
+
proc.onaudioprocess = (e) => {
|
|
580394
|
+
if (!_voiceWs || _voiceWs.readyState !== WebSocket.OPEN) return;
|
|
580395
|
+
if (_voicePlayingTts) return;
|
|
580396
|
+
const f32 = e.inputBuffer.getChannelData(0);
|
|
580397
|
+
const targetSR = 16000;
|
|
580398
|
+
const ctxSR = _voiceAudioCtx.sampleRate;
|
|
580399
|
+
let pcm16;
|
|
580400
|
+
if (ctxSR === targetSR) {
|
|
580401
|
+
pcm16 = new Int16Array(f32.length);
|
|
580402
|
+
for (let i = 0; i < f32.length; i++) {
|
|
580403
|
+
const s = Math.max(-1, Math.min(1, f32[i]));
|
|
580404
|
+
pcm16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
|
580405
|
+
}
|
|
580406
|
+
} else {
|
|
580407
|
+
const ratio = ctxSR / targetSR;
|
|
580408
|
+
const outLen = Math.floor(f32.length / ratio);
|
|
580409
|
+
pcm16 = new Int16Array(outLen);
|
|
580410
|
+
for (let i = 0; i < outLen; i++) {
|
|
580411
|
+
const idx = Math.floor(i * ratio);
|
|
580412
|
+
const s = Math.max(-1, Math.min(1, f32[idx] || 0));
|
|
580413
|
+
pcm16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
|
580414
|
+
}
|
|
580415
|
+
}
|
|
580416
|
+
try { _voiceWs.send(pcm16.buffer); } catch {}
|
|
580417
|
+
};
|
|
580418
|
+
src.connect(proc);
|
|
580419
|
+
proc.connect(_voiceAudioCtx.destination);
|
|
580420
|
+
_voiceMicNode = proc;
|
|
580421
|
+
}
|
|
580422
|
+
|
|
580423
|
+
function handleVoiceWsMessage(ev) {
|
|
580424
|
+
if (typeof ev.data === 'string') {
|
|
580425
|
+
try {
|
|
580426
|
+
const msg = JSON.parse(ev.data);
|
|
580427
|
+
if (msg.type === 'transcript') {
|
|
580428
|
+
addVoiceTranscriptLine(msg.final ? 'you' : 'you (partial)', msg.text);
|
|
580429
|
+
} else if (msg.type === 'agent_text') {
|
|
580430
|
+
addVoiceTranscriptLine('agent', msg.text);
|
|
580431
|
+
} else if (msg.type === 'state' || msg.type === 'session_state') {
|
|
580432
|
+
const pill = document.getElementById('voice-state-pill');
|
|
580433
|
+
if (pill) pill.textContent = msg.state;
|
|
580434
|
+
refreshVoiceState();
|
|
580435
|
+
} else if (msg.type === 'tts_header') {
|
|
580436
|
+
_voicePendingTtsHeader = { sampleRate: msg.sampleRate || 22050 };
|
|
580437
|
+
} else if (msg.type === 'tts_start') {
|
|
580438
|
+
_voicePlayingTts = true;
|
|
580439
|
+
} else if (msg.type === 'tts_end') {
|
|
580440
|
+
_voicePlayingTts = false;
|
|
580441
|
+
} else if (msg.type === 'error') {
|
|
580442
|
+
addVoiceTranscriptLine('error', msg.message);
|
|
580443
|
+
}
|
|
580444
|
+
} catch {}
|
|
580445
|
+
} else {
|
|
580446
|
+
const sr = (_voicePendingTtsHeader && _voicePendingTtsHeader.sampleRate) || 22050;
|
|
580447
|
+
_voicePendingTtsHeader = null;
|
|
580448
|
+
const pcm16 = new Int16Array(ev.data);
|
|
580449
|
+
playTtsPcm(pcm16, sr);
|
|
580450
|
+
}
|
|
580451
|
+
}
|
|
580452
|
+
|
|
580453
|
+
function playTtsPcm(pcm16, sampleRate) {
|
|
580454
|
+
if (!_voiceAudioCtx || _voiceAudioCtx.state === 'closed') return;
|
|
580455
|
+
const f32 = new Float32Array(pcm16.length);
|
|
580456
|
+
for (let i = 0; i < pcm16.length; i++) f32[i] = pcm16[i] / 0x8000;
|
|
580457
|
+
const buf = _voiceAudioCtx.createBuffer(1, f32.length, sampleRate);
|
|
580458
|
+
buf.copyToChannel(f32, 0);
|
|
580459
|
+
const src = _voiceAudioCtx.createBufferSource();
|
|
580460
|
+
src.buffer = buf;
|
|
580461
|
+
src.connect(_voiceAudioCtx.destination);
|
|
580462
|
+
src.start();
|
|
580463
|
+
}
|
|
580464
|
+
|
|
580465
|
+
function addVoiceTranscriptLine(speaker, text) {
|
|
580466
|
+
const pane = document.getElementById('voice-transcript-pane');
|
|
580467
|
+
if (!pane) return;
|
|
580468
|
+
const div = document.createElement('div');
|
|
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>';
|
|
580475
|
+
pane.appendChild(div);
|
|
580476
|
+
pane.scrollTop = pane.scrollHeight;
|
|
580477
|
+
while (pane.children.length > 200) pane.removeChild(pane.firstChild);
|
|
580478
|
+
}
|
|
580479
|
+
|
|
580480
|
+
window.__oaVoice = { startVoiceChat, stopVoiceChat, refreshVoiceState, loadCloneRefs };
|
|
580481
|
+
// ════════════════════════════════════════════════════════════════════
|
|
580482
|
+
|
|
580483
|
+
// ════════════════════════════════════════════════════════════════════
|
|
580484
|
+
// Subscription-driven fan-out: $currentProject is the single trigger
|
|
580485
|
+
// that refreshes everything project-scoped. Replaces the brittle
|
|
580486
|
+
// loadProjects/loadModels/loadChatSessions/loadConfig chain in the old
|
|
580487
|
+
// switch-on-click handler (which referenced a non-existent
|
|
580488
|
+
// loadChatSessions). Now each subscriber owns its own refresh and any
|
|
580489
|
+
// future feature can just subscribe once.
|
|
580490
|
+
// ════════════════════════════════════════════════════════════════════
|
|
580491
|
+
let _firstProjectSubscribe = true;
|
|
580492
|
+
$currentProject.subscribe((proj) => {
|
|
580493
|
+
// Skip the initial-null subscribe fire — first real project arrives
|
|
580494
|
+
// when loadProjects() resolves below.
|
|
580495
|
+
if (_firstProjectSubscribe) { _firstProjectSubscribe = false; return; }
|
|
580496
|
+
// The hydrate subscriber inside the store layer has already updated
|
|
580497
|
+
// $selectedModel / $chatSessionId / $agentRunId from server prefs.
|
|
580498
|
+
// These reactions just refresh data the model + sessions panels need.
|
|
580499
|
+
try { if (typeof loadModels === 'function') loadModels(); } catch {}
|
|
580500
|
+
try { if (typeof loadConfig === 'function') loadConfig(); } catch {}
|
|
580501
|
+
try { if (typeof updateSessionSelect === 'function') updateSessionSelect(); } catch {}
|
|
580502
|
+
try { if (typeof updateAgentRunSelect === 'function') updateAgentRunSelect(); } catch {}
|
|
580503
|
+
try { if (typeof restoreChatSession === 'function') restoreChatSession(); } catch {}
|
|
580504
|
+
});
|
|
580505
|
+
|
|
580506
|
+
// $selectedModel changes update the visible <select> if it's not
|
|
580507
|
+
// already in sync (handles cross-tab updates + server-side overrides).
|
|
580508
|
+
$selectedModel.subscribe((m) => {
|
|
580509
|
+
if (!m || !modelSelect) return;
|
|
580510
|
+
if (modelSelect.value !== m) {
|
|
580511
|
+
// Only set if option exists; otherwise loadModels will reconcile
|
|
580512
|
+
if (Array.from(modelSelect.options).some(o => o.value === m)) {
|
|
580513
|
+
modelSelect.value = m;
|
|
580514
|
+
}
|
|
580515
|
+
}
|
|
580516
|
+
});
|
|
580517
|
+
|
|
580518
|
+
// $activeTab changes flip the tab UI (one-way: subscriber → DOM).
|
|
580519
|
+
$activeTab.subscribe((tab) => {
|
|
580520
|
+
if (!tab || typeof switchTab !== 'function') return;
|
|
580521
|
+
// Avoid recursion: switchTab is called by user click and writes back
|
|
580522
|
+
// via window.switchTab('chat') style; we only DOM-update if needed.
|
|
580523
|
+
const tabBtn = document.getElementById('tab-' + tab);
|
|
580524
|
+
if (tabBtn && !tabBtn.classList.contains('active')) {
|
|
580525
|
+
try { switchTab(tab); } catch {}
|
|
580526
|
+
}
|
|
580527
|
+
});
|
|
580528
|
+
|
|
580529
|
+
// Init
|
|
580530
|
+
checkHealth();
|
|
580531
|
+
// Hydrate $currentProject from server before firing dependent loads.
|
|
580532
|
+
// loadProjects() fetches /v1/projects, sets $currentProject.set(current),
|
|
580533
|
+
// which cascades to loadModels/loadConfig/sessions/restoreChatSession.
|
|
580534
|
+
(async () => {
|
|
580535
|
+
await loadProjects();
|
|
580536
|
+
// If loadProjects didn't set a project (no current registered), still
|
|
580537
|
+
// fire a one-time fallback so the UI doesn't appear blank.
|
|
580538
|
+
if (!$currentProject.get()) {
|
|
580539
|
+
try { loadModels(); } catch {}
|
|
580540
|
+
try { updateSessionSelect(); } catch {}
|
|
580541
|
+
try { updateAgentRunSelect(); } catch {}
|
|
580542
|
+
try { restoreChatSession(); } catch {}
|
|
580543
|
+
}
|
|
580544
|
+
})();
|
|
580545
|
+
pollMetrics();
|
|
580546
|
+
setInterval(checkHealth, 30000);
|
|
580547
|
+
setInterval(pollMetrics, 10000);
|
|
580548
|
+
setInterval(loadScheduled, 15000);
|
|
580549
|
+
setInterval(loadServices, 30000);
|
|
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; }
|
|
579252
581388
|
}
|
|
579253
|
-
|
|
579254
|
-
|
|
579255
|
-
|
|
579256
|
-
|
|
579257
|
-
_voiceAudioCtx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
|
|
581389
|
+
// Cmd/Ctrl+, opens settings.
|
|
581390
|
+
if ((e.metaKey || e.ctrlKey) && e.key === ',') {
|
|
581391
|
+
e.preventDefault();
|
|
581392
|
+
openSettingsModal('general');
|
|
579258
581393
|
}
|
|
579259
|
-
|
|
579260
|
-
|
|
579261
|
-
|
|
579262
|
-
|
|
579263
|
-
|
|
579264
|
-
|
|
579265
|
-
|
|
579266
|
-
|
|
579267
|
-
|
|
579268
|
-
|
|
579269
|
-
|
|
579270
|
-
|
|
579271
|
-
|
|
579272
|
-
|
|
579273
|
-
|
|
579274
|
-
|
|
579275
|
-
|
|
579276
|
-
|
|
579277
|
-
|
|
579278
|
-
|
|
579279
|
-
|
|
579280
|
-
|
|
579281
|
-
|
|
579282
|
-
|
|
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);
|
|
579283
581422
|
}
|
|
579284
|
-
|
|
579285
|
-
|
|
579286
|
-
|
|
579287
|
-
proc.connect(_voiceAudioCtx.destination);
|
|
579288
|
-
_voiceMicNode = proc;
|
|
581423
|
+
} catch {}
|
|
581424
|
+
_slashLoaded = true;
|
|
581425
|
+
return _slashItems;
|
|
579289
581426
|
}
|
|
579290
581427
|
|
|
579291
|
-
function
|
|
579292
|
-
|
|
579293
|
-
|
|
579294
|
-
|
|
579295
|
-
if (msg.type === 'transcript') {
|
|
579296
|
-
addVoiceTranscriptLine(msg.final ? 'you' : 'you (partial)', msg.text);
|
|
579297
|
-
} else if (msg.type === 'agent_text') {
|
|
579298
|
-
addVoiceTranscriptLine('agent', msg.text);
|
|
579299
|
-
} else if (msg.type === 'state' || msg.type === 'session_state') {
|
|
579300
|
-
const pill = document.getElementById('voice-state-pill');
|
|
579301
|
-
if (pill) pill.textContent = msg.state;
|
|
579302
|
-
refreshVoiceState();
|
|
579303
|
-
} else if (msg.type === 'tts_header') {
|
|
579304
|
-
_voicePendingTtsHeader = { sampleRate: msg.sampleRate || 22050 };
|
|
579305
|
-
} else if (msg.type === 'tts_start') {
|
|
579306
|
-
_voicePlayingTts = true;
|
|
579307
|
-
} else if (msg.type === 'tts_end') {
|
|
579308
|
-
_voicePlayingTts = false;
|
|
579309
|
-
} else if (msg.type === 'error') {
|
|
579310
|
-
addVoiceTranscriptLine('error', msg.message);
|
|
579311
|
-
}
|
|
579312
|
-
} catch {}
|
|
579313
|
-
} else {
|
|
579314
|
-
const sr = (_voicePendingTtsHeader && _voicePendingTtsHeader.sampleRate) || 22050;
|
|
579315
|
-
_voicePendingTtsHeader = null;
|
|
579316
|
-
const pcm16 = new Int16Array(ev.data);
|
|
579317
|
-
playTtsPcm(pcm16, sr);
|
|
581428
|
+
function openSlashPalette() {
|
|
581429
|
+
const v = (input.value || '').trim();
|
|
581430
|
+
if (!v.startsWith('/')) {
|
|
581431
|
+
input.value = '/' + (input.value || '');
|
|
579318
581432
|
}
|
|
581433
|
+
input.focus();
|
|
581434
|
+
_maybeUpdateSlashPalette();
|
|
579319
581435
|
}
|
|
579320
581436
|
|
|
579321
|
-
function
|
|
579322
|
-
|
|
579323
|
-
|
|
579324
|
-
|
|
579325
|
-
const buf = _voiceAudioCtx.createBuffer(1, f32.length, sampleRate);
|
|
579326
|
-
buf.copyToChannel(f32, 0);
|
|
579327
|
-
const src = _voiceAudioCtx.createBufferSource();
|
|
579328
|
-
src.buffer = buf;
|
|
579329
|
-
src.connect(_voiceAudioCtx.destination);
|
|
579330
|
-
src.start();
|
|
581437
|
+
function closeSlashPalette() {
|
|
581438
|
+
const p = document.getElementById('slash-palette');
|
|
581439
|
+
if (p) p.style.display = 'none';
|
|
581440
|
+
_slashSelectedIdx = 0;
|
|
579331
581441
|
}
|
|
579332
581442
|
|
|
579333
|
-
function
|
|
579334
|
-
const
|
|
579335
|
-
|
|
579336
|
-
|
|
579337
|
-
|
|
579338
|
-
|
|
579339
|
-
|
|
579340
|
-
|
|
579341
|
-
|
|
579342
|
-
|
|
579343
|
-
|
|
579344
|
-
|
|
579345
|
-
|
|
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';
|
|
579346
581467
|
}
|
|
579347
581468
|
|
|
579348
|
-
|
|
579349
|
-
|
|
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
|
+
}
|
|
579350
581478
|
|
|
579351
|
-
|
|
579352
|
-
|
|
579353
|
-
|
|
579354
|
-
|
|
579355
|
-
|
|
579356
|
-
|
|
579357
|
-
|
|
579358
|
-
|
|
579359
|
-
|
|
579360
|
-
|
|
579361
|
-
|
|
579362
|
-
//
|
|
579363
|
-
|
|
579364
|
-
|
|
579365
|
-
|
|
579366
|
-
|
|
579367
|
-
|
|
579368
|
-
|
|
579369
|
-
|
|
579370
|
-
|
|
579371
|
-
try {
|
|
579372
|
-
|
|
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
|
+
}
|
|
579373
581504
|
|
|
579374
|
-
//
|
|
579375
|
-
|
|
579376
|
-
|
|
579377
|
-
|
|
579378
|
-
|
|
579379
|
-
// Only set if option exists; otherwise loadModels will reconcile
|
|
579380
|
-
if (Array.from(modelSelect.options).some(o => o.value === m)) {
|
|
579381
|
-
modelSelect.value = m;
|
|
579382
|
-
}
|
|
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 });
|
|
579383
581510
|
}
|
|
579384
|
-
|
|
581511
|
+
_renderAttachChips();
|
|
581512
|
+
}
|
|
579385
581513
|
|
|
579386
|
-
|
|
579387
|
-
|
|
579388
|
-
if (!
|
|
579389
|
-
|
|
579390
|
-
|
|
579391
|
-
|
|
579392
|
-
|
|
579393
|
-
try { switchTab(tab); } catch {}
|
|
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;
|
|
579394
581521
|
}
|
|
579395
|
-
|
|
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
|
+
})();
|
|
579396
581551
|
|
|
579397
|
-
//
|
|
579398
|
-
|
|
579399
|
-
//
|
|
579400
|
-
|
|
579401
|
-
//
|
|
579402
|
-
|
|
579403
|
-
|
|
579404
|
-
|
|
579405
|
-
|
|
579406
|
-
|
|
579407
|
-
|
|
579408
|
-
|
|
579409
|
-
|
|
579410
|
-
|
|
579411
|
-
}
|
|
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);
|
|
579412
581593
|
})();
|
|
579413
|
-
|
|
579414
|
-
|
|
579415
|
-
|
|
579416
|
-
|
|
579417
|
-
|
|
579418
|
-
|
|
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
|
+
|
|
579419
581604
|
input.focus();
|
|
579420
581605
|
</script>
|
|
581606
|
+
</div><!-- /#oa-main -->
|
|
581607
|
+
</div><!-- /#oa-shell -->
|
|
579421
581608
|
</body>
|
|
579422
581609
|
</html>`;
|
|
579423
581610
|
}
|
|
@@ -580086,6 +582273,137 @@ function getSwaggerUI() {
|
|
|
580086
582273
|
</body>
|
|
580087
582274
|
</html>`;
|
|
580088
582275
|
}
|
|
582276
|
+
function getRedocHTML() {
|
|
582277
|
+
return `<!DOCTYPE html>
|
|
582278
|
+
<html lang="en">
|
|
582279
|
+
<head>
|
|
582280
|
+
<meta charset="UTF-8">
|
|
582281
|
+
<title>Open Agents — API Reference (ReDoc)</title>
|
|
582282
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
582283
|
+
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
|
582284
|
+
<style>
|
|
582285
|
+
body { margin: 0; padding: 0; }
|
|
582286
|
+
#oa-banner { padding: 8px 14px; background: #1e1e22; color: #b0b0b0; font: 12px ui-monospace, monospace; border-bottom: 1px solid #2a2a30; }
|
|
582287
|
+
#oa-banner a { color: #b2920a; text-decoration: none; margin-right: 14px; }
|
|
582288
|
+
#oa-banner b { color: #b2920a; }
|
|
582289
|
+
</style>
|
|
582290
|
+
</head>
|
|
582291
|
+
<body>
|
|
582292
|
+
<div id="oa-banner">
|
|
582293
|
+
<b>Open Agents — ReDoc view</b>
|
|
582294
|
+
<a href="/api/docs">Swagger UI (interactive)</a>
|
|
582295
|
+
<a href="/openapi.json">openapi.json</a>
|
|
582296
|
+
<a href="/openapi.yaml">openapi.yaml</a>
|
|
582297
|
+
<a href="/asyncapi.json">asyncapi.json</a>
|
|
582298
|
+
<a href="/v1/routes">routes summary</a>
|
|
582299
|
+
<a href="/">root</a>
|
|
582300
|
+
</div>
|
|
582301
|
+
<redoc spec-url="/openapi.json"></redoc>
|
|
582302
|
+
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
|
|
582303
|
+
</body>
|
|
582304
|
+
</html>`;
|
|
582305
|
+
}
|
|
582306
|
+
function getAsyncApiSpec() {
|
|
582307
|
+
const oa = getOpenApiSpec();
|
|
582308
|
+
const clientSchema = oa.components?.schemas?.VoicechatClientFrame;
|
|
582309
|
+
const serverSchema = oa.components?.schemas?.VoicechatServerFrame;
|
|
582310
|
+
return {
|
|
582311
|
+
asyncapi: "2.6.0",
|
|
582312
|
+
info: {
|
|
582313
|
+
title: "Open Agents — Voicechat (WebSocket)",
|
|
582314
|
+
version: "1.0.0",
|
|
582315
|
+
description: "Full-duplex voicechat over WebSocket. Binary frames carry PCM audio (mic in 16 kHz mono Int16 LE; TTS out at the model's native sample rate, announced in the preceding `tts_header` text frame). Text frames carry JSON control messages.",
|
|
582316
|
+
license: { name: "CC-BY-NC-4.0", url: "https://creativecommons.org/licenses/by-nc/4.0/" }
|
|
582317
|
+
},
|
|
582318
|
+
servers: {
|
|
582319
|
+
local: {
|
|
582320
|
+
url: "ws://localhost:11435",
|
|
582321
|
+
protocol: "ws",
|
|
582322
|
+
description: "Daemon on localhost. The same path works on LAN IP, public-IP forwarding, or behind a TLS-terminating proxy (then `wss://`)."
|
|
582323
|
+
}
|
|
582324
|
+
},
|
|
582325
|
+
defaultContentType: "application/json",
|
|
582326
|
+
channels: {
|
|
582327
|
+
"/v1/voicechat/ws": {
|
|
582328
|
+
description: "Voicechat full-duplex channel. Open with `?user=<displayName>` to set the connected-user name.",
|
|
582329
|
+
parameters: {
|
|
582330
|
+
user: { description: "Optional display name (default: 'browser').", schema: { type: "string" } }
|
|
582331
|
+
},
|
|
582332
|
+
publish: {
|
|
582333
|
+
summary: "Frames the client sends to the server (binary mic PCM + JSON control).",
|
|
582334
|
+
operationId: "publishToVoicechat",
|
|
582335
|
+
message: {
|
|
582336
|
+
oneOf: [
|
|
582337
|
+
{ name: "MicAudio", title: "Microphone PCM", contentType: "application/octet-stream", summary: "Binary frame: PCM Int16 mono 16 kHz from the user's mic (typically 4096 samples per frame).", payload: { type: "string", format: "binary" } },
|
|
582338
|
+
{ name: "ClientControl", title: "JSON control", payload: { $ref: "#/components/schemas/VoicechatClientFrame" } }
|
|
582339
|
+
]
|
|
582340
|
+
}
|
|
582341
|
+
},
|
|
582342
|
+
subscribe: {
|
|
582343
|
+
summary: "Frames the server sends to the client (binary TTS PCM + JSON control).",
|
|
582344
|
+
operationId: "subscribeToVoicechat",
|
|
582345
|
+
message: {
|
|
582346
|
+
oneOf: [
|
|
582347
|
+
{ name: "TtsAudio", title: "TTS PCM", contentType: "application/octet-stream", summary: "Binary frame: PCM Int16 mono at the model's native sample rate. ALWAYS preceded by a `tts_header` JSON frame announcing `sampleRate`.", payload: { type: "string", format: "binary" } },
|
|
582348
|
+
{ name: "ServerControl", title: "JSON control", payload: { $ref: "#/components/schemas/VoicechatServerFrame" } }
|
|
582349
|
+
]
|
|
582350
|
+
}
|
|
582351
|
+
}
|
|
582352
|
+
}
|
|
582353
|
+
},
|
|
582354
|
+
components: {
|
|
582355
|
+
schemas: {
|
|
582356
|
+
VoicechatClientFrame: clientSchema,
|
|
582357
|
+
VoicechatServerFrame: serverSchema
|
|
582358
|
+
}
|
|
582359
|
+
}
|
|
582360
|
+
};
|
|
582361
|
+
}
|
|
582362
|
+
function jsonToYaml(value2, indent = 0) {
|
|
582363
|
+
const pad = (n2) => " ".repeat(n2);
|
|
582364
|
+
if (value2 === null) return "null";
|
|
582365
|
+
if (value2 === void 0) return "null";
|
|
582366
|
+
if (typeof value2 === "boolean") return value2 ? "true" : "false";
|
|
582367
|
+
if (typeof value2 === "number") return Number.isFinite(value2) ? String(value2) : "null";
|
|
582368
|
+
if (typeof value2 === "string") {
|
|
582369
|
+
if (value2.length === 0) return '""';
|
|
582370
|
+
if (/[\n\r]/.test(value2)) {
|
|
582371
|
+
const lines = value2.replace(/\r\n?/g, "\n").split("\n");
|
|
582372
|
+
return "|-\n" + lines.map((l2) => pad(indent + 1) + l2).join("\n");
|
|
582373
|
+
}
|
|
582374
|
+
if (/^(true|false|null|yes|no|on|off|~|\d|-\d|\.\d|\.inf|\.nan|@|`|\*|&|!|\?|\||>|%|"|')/i.test(value2) || /[#:,\[\]{}]/.test(value2) || /^\s|\s$/.test(value2)) {
|
|
582375
|
+
return JSON.stringify(value2);
|
|
582376
|
+
}
|
|
582377
|
+
return value2;
|
|
582378
|
+
}
|
|
582379
|
+
if (Array.isArray(value2)) {
|
|
582380
|
+
if (value2.length === 0) return "[]";
|
|
582381
|
+
return "\n" + value2.map((item) => {
|
|
582382
|
+
const rendered = jsonToYaml(item, indent + 1);
|
|
582383
|
+
if (rendered.startsWith("\n")) {
|
|
582384
|
+
return pad(indent) + "-" + rendered.replace(/^\n/, "\n");
|
|
582385
|
+
}
|
|
582386
|
+
const lines = rendered.split("\n");
|
|
582387
|
+
if (lines.length === 1) return pad(indent) + "- " + lines[0];
|
|
582388
|
+
return pad(indent) + "- " + lines[0] + "\n" + lines.slice(1).map((l2) => pad(indent + 1) + l2.replace(/^ /, "")).join("\n");
|
|
582389
|
+
}).join("\n");
|
|
582390
|
+
}
|
|
582391
|
+
if (typeof value2 === "object") {
|
|
582392
|
+
const obj = value2;
|
|
582393
|
+
const keys = Object.keys(obj);
|
|
582394
|
+
if (keys.length === 0) return "{}";
|
|
582395
|
+
return "\n" + keys.map((k) => {
|
|
582396
|
+
const v = obj[k];
|
|
582397
|
+
const safeKey = /^[a-zA-Z_][a-zA-Z0-9_./-]*$/.test(k) && !/^(true|false|null|yes|no|on|off)$/i.test(k) ? k : JSON.stringify(k);
|
|
582398
|
+
const rendered = jsonToYaml(v, indent + 1);
|
|
582399
|
+
if (rendered.startsWith("\n")) {
|
|
582400
|
+
return pad(indent) + safeKey + ":" + rendered;
|
|
582401
|
+
}
|
|
582402
|
+
return pad(indent) + safeKey + ": " + rendered;
|
|
582403
|
+
}).join("\n");
|
|
582404
|
+
}
|
|
582405
|
+
return JSON.stringify(value2);
|
|
582406
|
+
}
|
|
580089
582407
|
var SWAGGER_VERSION;
|
|
580090
582408
|
var init_openapi = __esm({
|
|
580091
582409
|
"packages/cli/src/api/openapi.ts"() {
|
|
@@ -583637,6 +585955,103 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
|
|
|
583637
585955
|
return;
|
|
583638
585956
|
}
|
|
583639
585957
|
}
|
|
585958
|
+
if ((pathname === "/v3/api-docs" || pathname === "/swagger.json" || pathname === "/api-docs") && method === "GET") {
|
|
585959
|
+
jsonResponse(res, 200, getOpenApiSpec());
|
|
585960
|
+
return;
|
|
585961
|
+
}
|
|
585962
|
+
if (pathname === "/openapi.yaml" && method === "GET") {
|
|
585963
|
+
const spec = getOpenApiSpec();
|
|
585964
|
+
const yaml = jsonToYaml(spec);
|
|
585965
|
+
res.writeHead(200, {
|
|
585966
|
+
"Content-Type": "application/yaml; charset=utf-8",
|
|
585967
|
+
"Cache-Control": "public, max-age=60"
|
|
585968
|
+
});
|
|
585969
|
+
res.end(yaml);
|
|
585970
|
+
return;
|
|
585971
|
+
}
|
|
585972
|
+
if ((pathname === "/swagger-ui" || pathname === "/swagger-ui/" || pathname === "/swagger-ui/index.html") && method === "GET") {
|
|
585973
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
585974
|
+
res.end(getSwaggerUI());
|
|
585975
|
+
return;
|
|
585976
|
+
}
|
|
585977
|
+
if ((pathname === "/redoc" || pathname === "/redoc/") && method === "GET") {
|
|
585978
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
585979
|
+
res.end(getRedocHTML());
|
|
585980
|
+
return;
|
|
585981
|
+
}
|
|
585982
|
+
if ((pathname === "/asyncapi.json" || pathname === "/asyncapi") && method === "GET") {
|
|
585983
|
+
jsonResponse(res, 200, getAsyncApiSpec());
|
|
585984
|
+
return;
|
|
585985
|
+
}
|
|
585986
|
+
if (pathname === "/" && method === "GET" && !req2.headers.accept?.includes("text/html")) {
|
|
585987
|
+
const cfg = loadConfig();
|
|
585988
|
+
const baseUrl = `${req2.headers["x-forwarded-proto"] || (req2.socket && req2.socket.encrypted ? "https" : "http")}://${req2.headers.host || "localhost"}`;
|
|
585989
|
+
jsonResponse(res, 200, {
|
|
585990
|
+
name: "open-agents",
|
|
585991
|
+
version: API_VERSION,
|
|
585992
|
+
description: "Open Agents AI — local agentic stack with OpenAI-compatible inference, voice (TTS/ASR/voice-clone), per-project preferences, AIMS controls, and full WebSocket voicechat. All endpoints documented in /openapi.json + interactive at /api/docs.",
|
|
585993
|
+
documentation_url: `${baseUrl}/api/docs`,
|
|
585994
|
+
openapi_spec_url: `${baseUrl}/openapi.json`,
|
|
585995
|
+
asyncapi_spec_url: `${baseUrl}/asyncapi.json`,
|
|
585996
|
+
backend: { url: cfg.backendUrl, type: cfg.backendType, model: cfg.model },
|
|
585997
|
+
// _links shape mirrors GitHub's API root + JSON-Home flavor.
|
|
585998
|
+
// Each cluster has a one-line description so an agent doesn't need
|
|
585999
|
+
// to read the OpenAPI spec to know what's there.
|
|
586000
|
+
_links: {
|
|
586001
|
+
self: `${baseUrl}/`,
|
|
586002
|
+
health: { href: `${baseUrl}/health`, description: "Liveness + version + uptime" },
|
|
586003
|
+
openapi: { href: `${baseUrl}/openapi.json`, description: "OpenAPI 3.0 spec (machine-readable). Aliases: /openapi.yaml, /v3/api-docs, /swagger.json, /api-docs." },
|
|
586004
|
+
asyncapi: { href: `${baseUrl}/asyncapi.json`, description: "AsyncAPI 2.6 spec for the /v1/voicechat/ws WebSocket." },
|
|
586005
|
+
docs: { href: `${baseUrl}/api/docs`, description: "Interactive Swagger UI. Aliases: /docs, /swagger-ui." },
|
|
586006
|
+
redoc: { href: `${baseUrl}/redoc`, description: "ReDoc renderer of the same /openapi.json." },
|
|
586007
|
+
routes: { href: `${baseUrl}/v1/routes`, description: "Flat grep-friendly summary of every route (~5 KB)." },
|
|
586008
|
+
chat: {
|
|
586009
|
+
completions: { href: `${baseUrl}/v1/chat/completions`, description: "OpenAI-compatible chat completion. Native tool calling supported." },
|
|
586010
|
+
session: { href: `${baseUrl}/v1/chat`, description: "Stateful chat session with optional agent subprocess (set tools=true for full tool stack)." },
|
|
586011
|
+
sessions: { href: `${baseUrl}/v1/chat/sessions`, description: "List active chat sessions." }
|
|
586012
|
+
},
|
|
586013
|
+
voice: {
|
|
586014
|
+
state: { href: `${baseUrl}/v1/voice/state`, description: "Voice runtime status." },
|
|
586015
|
+
tts_models: { href: `${baseUrl}/v1/voice/models`, description: "List TTS voice models." },
|
|
586016
|
+
tts: { href: `${baseUrl}/v1/voice/tts`, description: "Synthesize text → audio bytes (also at /v1/audio/speech for OpenAI compat)." },
|
|
586017
|
+
asr_models: { href: `${baseUrl}/v1/voice/asr-models`, description: "List Whisper ASR models." },
|
|
586018
|
+
transcribe: { href: `${baseUrl}/v1/voice/transcribe`, description: "Transcribe audio → text (also at /v1/audio/transcriptions)." },
|
|
586019
|
+
transcribe_stream: { href: `${baseUrl}/v1/voice/transcribe/stream`, description: "Streaming ASR via SSE." },
|
|
586020
|
+
voicechat_ws: { href: `${baseUrl}/v1/voicechat/ws`, description: "Full-duplex voicechat WebSocket. See /asyncapi.json for the frame schemas." },
|
|
586021
|
+
clone_refs: { href: `${baseUrl}/v1/voice/clone-refs`, description: "Voice clone reference management. Upload via JSON+base64, raw multipart, or from-URL." },
|
|
586022
|
+
speak: { href: `${baseUrl}/v1/voice/speak`, description: "Synthesize text and broadcast to /v1/voicechat/ws clients." }
|
|
586023
|
+
},
|
|
586024
|
+
projects: {
|
|
586025
|
+
list: { href: `${baseUrl}/v1/projects`, description: "Workspace registry." },
|
|
586026
|
+
current: { href: `${baseUrl}/v1/projects/current`, description: "Active project." },
|
|
586027
|
+
switch: { href: `${baseUrl}/v1/projects/switch`, description: "Set the active project (POST)." },
|
|
586028
|
+
preferences: { href: `${baseUrl}/v1/projects/preferences`, description: "Per-project UI preferences (model, theme, current chat session, etc.)." }
|
|
586029
|
+
},
|
|
586030
|
+
inference: {
|
|
586031
|
+
models: { href: `${baseUrl}/v1/models`, description: "OpenAI-compatible model list." },
|
|
586032
|
+
embeddings: { href: `${baseUrl}/v1/embeddings`, description: "Generate embeddings." }
|
|
586033
|
+
},
|
|
586034
|
+
agentic: {
|
|
586035
|
+
run: { href: `${baseUrl}/v1/run`, description: "Submit autonomous task." },
|
|
586036
|
+
runs: { href: `${baseUrl}/v1/runs`, description: "List runs." },
|
|
586037
|
+
tools: { href: `${baseUrl}/v1/tools`, description: "Agentic tool registry." },
|
|
586038
|
+
mcps: { href: `${baseUrl}/v1/mcps`, description: "MCP server registry." }
|
|
586039
|
+
},
|
|
586040
|
+
memory: {
|
|
586041
|
+
root: { href: `${baseUrl}/v1/memory`, description: "Memory backends summary." },
|
|
586042
|
+
search: { href: `${baseUrl}/v1/memory/search`, description: "Search memory." },
|
|
586043
|
+
episodes: { href: `${baseUrl}/v1/memory/episodes`, description: "List episodes." }
|
|
586044
|
+
},
|
|
586045
|
+
observability: {
|
|
586046
|
+
usage: { href: `${baseUrl}/v1/usage`, description: "Token usage + rate limits." },
|
|
586047
|
+
audit: { href: `${baseUrl}/v1/audit`, description: "Audit log." },
|
|
586048
|
+
events: { href: `${baseUrl}/v1/events`, description: "SSE event bus." }
|
|
586049
|
+
},
|
|
586050
|
+
aims: { href: `${baseUrl}/v1/aims`, description: "ISO/IEC 42001:2023 AIMS controls (policies, roles, resources, impact assessments, …)." }
|
|
586051
|
+
}
|
|
586052
|
+
});
|
|
586053
|
+
return;
|
|
586054
|
+
}
|
|
583640
586055
|
if (pathname === "/" && method === "GET" && req2.headers.accept?.includes("text/html")) {
|
|
583641
586056
|
res.writeHead(200, {
|
|
583642
586057
|
"Content-Type": "text/html; charset=utf-8",
|