pi-mega-compact 0.20.49 → 0.20.51

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.
Files changed (85) hide show
  1. package/dist/config/vector-cortex-enc1a.js +39 -0
  2. package/dist/config/vector-cortex-enc1b.js +46 -0
  3. package/dist/config/vector-cortex.js +2 -0
  4. package/dist/config.js +1 -1
  5. package/dist/extensions/dashboard-server/routes-rag-settings-vector-cortex.js +2 -0
  6. package/dist/extensions/dashboard-server/routes-setup-enc1a.js +157 -0
  7. package/dist/extensions/dashboard-server/routes-setup-enc1b.js +297 -0
  8. package/dist/extensions/dashboard-server/routes-setup-env-upsert.js +79 -0
  9. package/dist/extensions/dashboard-server/routes-setup.js +47 -25
  10. package/dist/src/config/vector-cortex-enc1a.js +39 -0
  11. package/dist/src/config/vector-cortex-enc1b.js +46 -0
  12. package/dist/src/config/vector-cortex.js +2 -0
  13. package/dist/src/config.js +1 -1
  14. package/extensions/dashboard-client/dist/assets/{AreaChart-COnj8FHi.js → AreaChart-BFTUTmmW.js} +2 -2
  15. package/extensions/dashboard-client/dist/assets/{AreaChart-COnj8FHi.js.map → AreaChart-BFTUTmmW.js.map} +1 -1
  16. package/extensions/dashboard-client/dist/assets/{BarChart-BYqt8c5-.js → BarChart-DfFurESR.js} +2 -2
  17. package/extensions/dashboard-client/dist/assets/{BarChart-BYqt8c5-.js.map → BarChart-DfFurESR.js.map} +1 -1
  18. package/extensions/dashboard-client/dist/assets/{CacheTab-PLRjMrZV.js → CacheTab-FpCJSmy8.js} +2 -2
  19. package/extensions/dashboard-client/dist/assets/{CacheTab-PLRjMrZV.js.map → CacheTab-FpCJSmy8.js.map} +1 -1
  20. package/extensions/dashboard-client/dist/assets/{EventsTab-DXKSft6w.js → EventsTab--9lJFJtc.js} +2 -2
  21. package/extensions/dashboard-client/dist/assets/{EventsTab-DXKSft6w.js.map → EventsTab--9lJFJtc.js.map} +1 -1
  22. package/extensions/dashboard-client/dist/assets/{HealthTab-BgVIsrde.js → HealthTab-CkgtF1qa.js} +2 -2
  23. package/extensions/dashboard-client/dist/assets/{HealthTab-BgVIsrde.js.map → HealthTab-CkgtF1qa.js.map} +1 -1
  24. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DNgFC8KZ.js → MaintenanceTab-ChXGq1or.js} +2 -2
  25. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DNgFC8KZ.js.map → MaintenanceTab-ChXGq1or.js.map} +1 -1
  26. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-B6yY47sz.js → MemoryMapTab-CPpRR4MV.js} +2 -2
  27. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-B6yY47sz.js.map → MemoryMapTab-CPpRR4MV.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/{MetricsTab-BrhJ6rkx.js → MetricsTab-Db1wkykf.js} +2 -2
  29. package/extensions/dashboard-client/dist/assets/{MetricsTab-BrhJ6rkx.js.map → MetricsTab-Db1wkykf.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/{OverviewTab-RFHU9L4x.js → OverviewTab-LanDECrz.js} +2 -2
  31. package/extensions/dashboard-client/dist/assets/{OverviewTab-RFHU9L4x.js.map → OverviewTab-LanDECrz.js.map} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{ReposTab-UFIWKkJM.js → ReposTab-I2umKduk.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{ReposTab-UFIWKkJM.js.map → ReposTab-I2umKduk.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/{SessionsTab-BHFYsmK8.js → SessionsTab-D_IxuEt5.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{SessionsTab-BHFYsmK8.js.map → SessionsTab-D_IxuEt5.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/SetupTab-BZ7Jn5MR.js +3 -0
  37. package/extensions/dashboard-client/dist/assets/SetupTab-BZ7Jn5MR.js.map +1 -0
  38. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-aPG5Knqn.js → TimeSavedCard-BTdcO1E2.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-aPG5Knqn.js.map → TimeSavedCard-BTdcO1E2.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{TurnsTab-fGm9n-Tk.js → TurnsTab-BXC8IL_B.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{TurnsTab-fGm9n-Tk.js.map → TurnsTab-BXC8IL_B.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-BtmSNH8X.js → VcStatusBadge-CNJnFtJo.js} +2 -2
  43. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-BtmSNH8X.js.map → VcStatusBadge-CNJnFtJo.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-meALMMOZ.js → VectorCortexTab-fsuWgA6e.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-meALMMOZ.js.map → VectorCortexTab-fsuWgA6e.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{WikiTab-0TK6q6g1.js → WikiTab-Cn1BScDU.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{WikiTab-0TK6q6g1.js.map → WikiTab-Cn1BScDU.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{button-CKEnMZ98.js → button-B4_9rsul.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{button-CKEnMZ98.js.map → button-B4_9rsul.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{card-BVVhm3sL.js → card-DUvC_geI.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{card-BVVhm3sL.js.map → card-DUvC_geI.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{client-extra-BvGxKZpm.js → client-extra-HaH97bzp.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{client-extra-BvGxKZpm.js.map → client-extra-HaH97bzp.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-B1PeCct1.js → generateCategoricalChart-BxFWU2Mt.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-B1PeCct1.js.map → generateCategoricalChart-BxFWU2Mt.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/index-C_aDd9kk.css +1 -0
  57. package/extensions/dashboard-client/dist/assets/{index-DuThOA-d.js → index-xPdbZ73U.js} +3 -3
  58. package/extensions/dashboard-client/dist/assets/{index-DuThOA-d.js.map → index-xPdbZ73U.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{switch-7qLS7ajc.js → switch-Bv3_1R_j.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{switch-7qLS7ajc.js.map → switch-Bv3_1R_j.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{toggle-D7mgOVxz.js → toggle-DHXrB3Aw.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{toggle-D7mgOVxz.js.map → toggle-DHXrB3Aw.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{useSSE-ChpyZVeX.js → useSSE-NigHeFyT.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{useSSE-ChpyZVeX.js.map → useSSE-NigHeFyT.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/index.html +2 -2
  66. package/extensions/dashboard-client/src/tabs/SetupTab/CortexRuntimeCard.tsx +103 -0
  67. package/extensions/dashboard-client/src/tabs/SetupTab/CortexRuntimeCardStyles.ts +49 -0
  68. package/extensions/dashboard-client/src/tabs/SetupTab/CortexSetup.tsx +2 -0
  69. package/extensions/dashboard-client/src/tabs/SetupTab/CustomEndpointSection.tsx +178 -2
  70. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +61 -113
  71. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetupHelpers.tsx +125 -0
  72. package/extensions/dashboard-server/api-contracts/setup.ts +47 -0
  73. package/extensions/dashboard-server/routes-rag-settings-vector-cortex.ts +12 -0
  74. package/extensions/dashboard-server/routes-setup-enc1a.ts +184 -0
  75. package/extensions/dashboard-server/routes-setup-enc1b.ts +336 -0
  76. package/extensions/dashboard-server/routes-setup-env-upsert.ts +93 -0
  77. package/extensions/dashboard-server/routes-setup.ts +46 -23
  78. package/package.json +1 -1
  79. package/src/config/vector-cortex-enc1a.ts +41 -0
  80. package/src/config/vector-cortex-enc1b.ts +50 -0
  81. package/src/config/vector-cortex.ts +9 -0
  82. package/src/config.ts +2 -0
  83. package/extensions/dashboard-client/dist/assets/SetupTab-C3dnKa8y.js +0 -3
  84. package/extensions/dashboard-client/dist/assets/SetupTab-C3dnKa8y.js.map +0 -1
  85. package/extensions/dashboard-client/dist/assets/index-DbuGwYD7.css +0 -1
@@ -2,15 +2,46 @@
2
2
  * SetupTab/CustomEndpointSection.tsx — custom remote embedder endpoint section.
3
3
  *
4
4
  * Extracted from EmbedderSetup.tsx to respect the extensions/ 400-line soft limit.
5
+ *
6
+ * ENC-1a: an additive "API key (optional Bearer)" password input sits beside the
7
+ * existing custom-URL input. The key is NEVER prefilled (the setup-status GET does
8
+ * not echo it — only an "is set" boolean, embeddingApiKeySet) and it is only ever
9
+ * sent up via the POST /api/setup-configure body. When the ENC-1a surface is
10
+ * inactive (flag off, the server omits the new GET fields) the key input is hidden
11
+ * and only the pre-existing URL row remains — byte-identical to the predecessor.
5
12
  */
6
13
  import type React from "react";
14
+ import { useState } from "react";
15
+ import type { SetupConfigureResponse } from "@contracts";
7
16
  import { styles } from "./EmbedderSetupStyles";
8
17
 
18
+ /** ENC-1a surfaced a successful custom-endpoint persistence on the card. */
19
+ const SAVED_NOTICE = "Saved; takes effect on next session start";
20
+
9
21
  interface CustomEndpointSectionProps {
10
22
  customUrl: string;
11
23
  setCustomUrl: (v: string) => void;
12
- applyEmbedder: (embedder: "ollama" | "llama" | "trigram" | "custom" | "onnx", url?: string) => void;
24
+ applyEmbedder: (
25
+ embedder: "ollama" | "llama" | "trigram" | "custom" | "onnx",
26
+ url?: string,
27
+ apiKey?: string,
28
+ dim?: string,
29
+ headers?: string,
30
+ allowRemote?: boolean,
31
+ ) => void;
13
32
  configuring: string | null;
33
+ /** ENC-1a surface active — true when the new setup-status fields are present. */
34
+ inputEnabled: boolean;
35
+ /** Server-reported "key is set" marker (the key itself is never returned). */
36
+ apiKeySet: boolean;
37
+ /** ENC-1b: the persisted `embeddingDim` value (numeric string), or undefined. One-shot seed. */
38
+ embeddingDim: string | undefined;
39
+ /** ENC-1b: server-reported "headers are set" marker (the raw JSON is never returned). */
40
+ embeddingHeadersSet: boolean;
41
+ /** ENC-1b: the persisted allow-remote flag, or undefined. One-shot seed. */
42
+ allowRemoteEmbedder: boolean | undefined;
43
+ configureResult: SetupConfigureResponse | null;
44
+ configureError: string | null;
14
45
  }
15
46
 
16
47
  export default function CustomEndpointSection({
@@ -18,7 +49,38 @@ export default function CustomEndpointSection({
18
49
  setCustomUrl,
19
50
  applyEmbedder,
20
51
  configuring,
52
+ inputEnabled,
53
+ apiKeySet,
54
+ embeddingDim,
55
+ embeddingHeadersSet,
56
+ allowRemoteEmbedder,
57
+ configureResult,
58
+ configureError,
21
59
  }: CustomEndpointSectionProps): React.ReactElement {
60
+ // The API key is intentionally local-only state: it always renders blank and
61
+ // is never seeded from the persisted value (the server never returns it).
62
+ const [customApiKey, setCustomApiKey] = useState("");
63
+ // ENC-1b: the embedding dim, seeded once from the persisted value (never
64
+ // re-seeded after a poll so the user's in-flight edits survive the 5s poll).
65
+ const [dim, setDim] = useState("");
66
+ const [dimSeeded, setDimSeeded] = useState(false);
67
+ // ENC-1b: the headers JSON is local-only, write-only state: NEVER prefilled,
68
+ // never seeded, never shown back — the server persists it verbatim and only
69
+ // ever reports its presence via `embeddingHeadersSet`.
70
+ const [headers, setHeaders] = useState("");
71
+ // ENC-1b: allow-remote toggle, seeded once from the persisted flag.
72
+ const [allowRemote, setAllowRemote] = useState(false);
73
+ const [allowRemoteSeeded, setAllowRemoteSeeded] = useState(false);
74
+
75
+ if (inputEnabled && !dimSeeded && typeof embeddingDim === "string") {
76
+ setDim(embeddingDim);
77
+ setDimSeeded(true);
78
+ }
79
+ if (inputEnabled && !allowRemoteSeeded && typeof allowRemoteEmbedder === "boolean") {
80
+ setAllowRemote(allowRemoteEmbedder);
81
+ setAllowRemoteSeeded(true);
82
+ }
83
+
22
84
  return (
23
85
  <div style={styles.section}>
24
86
  <h3 style={styles.sectionTitle}>Custom Endpoint (remote / third-party)</h3>
@@ -46,14 +108,128 @@ export default function CustomEndpointSection({
46
108
  fontFamily: "monospace",
47
109
  }}
48
110
  />
111
+ {inputEnabled && (
112
+ <input
113
+ type="password"
114
+ autoComplete="new-password"
115
+ aria-label="API key (optional Bearer)"
116
+ placeholder="API key (optional Bearer)"
117
+ value={customApiKey}
118
+ onChange={(e) => setCustomApiKey(e.target.value)}
119
+ style={{
120
+ flex: "1 1 300px",
121
+ background: "#12122a",
122
+ color: "#e0e0e0",
123
+ border: "1px solid #2a2a4e",
124
+ borderRadius: "6px",
125
+ padding: "0.4rem 0.6rem",
126
+ fontSize: "0.85rem",
127
+ fontFamily: "monospace",
128
+ }}
129
+ />
130
+ )}
131
+ {inputEnabled && apiKeySet && (
132
+ <span
133
+ style={{
134
+ fontSize: "0.75rem",
135
+ color: "#81c784",
136
+ border: "1px solid #2a4e2a",
137
+ borderRadius: "4px",
138
+ padding: "0.15rem 0.45rem",
139
+ }}
140
+ >
141
+ API key saved
142
+ </span>
143
+ )}
144
+ {inputEnabled && embeddingHeadersSet && (
145
+ <span
146
+ style={{
147
+ fontSize: "0.75rem",
148
+ color: "#81c784",
149
+ border: "1px solid #2a4e2a",
150
+ borderRadius: "4px",
151
+ padding: "0.15rem 0.45rem",
152
+ }}
153
+ >
154
+ Headers saved
155
+ </span>
156
+ )}
49
157
  <button
50
158
  style={{ ...styles.button, opacity: customUrl ? 1 : 0.4 }}
51
- onClick={() => applyEmbedder("custom", customUrl)}
159
+ onClick={() =>
160
+ applyEmbedder("custom", customUrl, customApiKey, dim, headers, allowRemote)
161
+ }
52
162
  disabled={!customUrl || configuring !== null}
53
163
  >
54
164
  {configuring === "custom" ? "Writing..." : "Use Custom URL"}
55
165
  </button>
56
166
  </div>
167
+ {inputEnabled && (
168
+ <div style={{ marginTop: "0.75rem" }}>
169
+ <div style={{ marginBottom: "0.5rem" }}>
170
+ <label style={{ ...styles.label, display: "block", marginBottom: "0.25rem" }}>
171
+ Embedding dim (optional)
172
+ </label>
173
+ <input
174
+ type="number"
175
+ min={1}
176
+ placeholder="e.g. 384"
177
+ value={dim}
178
+ onChange={(e) => setDim(e.target.value)}
179
+ style={{
180
+ width: "200px",
181
+ background: "#12122a",
182
+ color: "#e0e0e0",
183
+ border: "1px solid #2a2a4e",
184
+ borderRadius: "6px",
185
+ padding: "0.4rem 0.6rem",
186
+ fontSize: "0.85rem",
187
+ fontFamily: "monospace",
188
+ }}
189
+ />
190
+ </div>
191
+ <div style={{ marginBottom: "0.5rem" }}>
192
+ <label style={{ ...styles.label, display: "block", marginBottom: "0.25rem" }}>
193
+ Embedding headers (JSON, write-only — never re-displayed)
194
+ </label>
195
+ <textarea
196
+ placeholder={'{"Authorization": "Bearer ..."}'}
197
+ value={headers}
198
+ onChange={(e) => setHeaders(e.target.value)}
199
+ aria-label="Embedding headers JSON (write-only — never re-displayed)"
200
+ rows={2}
201
+ style={{
202
+ width: "100%",
203
+ background: "#12122a",
204
+ color: "#e0e0e0",
205
+ border: "1px solid #2a2a4e",
206
+ borderRadius: "6px",
207
+ padding: "0.4rem 0.6rem",
208
+ fontSize: "0.85rem",
209
+ fontFamily: "monospace",
210
+ resize: "vertical",
211
+ }}
212
+ />
213
+ </div>
214
+ <label style={{ display: "flex", alignItems: "center", gap: "0.5rem", fontSize: "0.85rem" }}>
215
+ <input
216
+ type="checkbox"
217
+ checked={allowRemote}
218
+ onChange={(e) => setAllowRemote(e.target.checked)}
219
+ aria-label="Allow remote embedder (skips loopback-only check)"
220
+ />
221
+ Allow remote embedder (skips loopback-only check)
222
+ </label>
223
+ </div>
224
+ )}
225
+ {configureError !== null && (
226
+ <p style={{ color: "#f44336", fontSize: "0.85rem", marginTop: "0.5rem" }}>
227
+ Configure error: {configureError}
228
+ </p>
229
+ )}
230
+ {configureResult !== null && configureResult.embedder === "custom" && (
231
+ <div style={styles.warning}>{SAVED_NOTICE}</div>
232
+ )}
57
233
  <div style={styles.warning}>
58
234
  <strong>Heads up:</strong> a remote endpoint receives the text you embed
59
235
  (it does NOT receive your full conversation — only the strings passed to
@@ -16,6 +16,7 @@ import EmbedderHealthCard from "./EmbedderHealthCard";
16
16
  import SettingsPanel from "./SettingsPanel";
17
17
  import CustomEndpointSection from "./CustomEndpointSection";
18
18
  import { styles } from "./EmbedderSetupStyles";
19
+ import { detectBadge, CurrentConfigSection } from "./EmbedderSetupHelpers";
19
20
 
20
21
  export default function EmbedderSetup(): React.ReactElement {
21
22
  const [status, setStatus] = useState<SetupStatusResponse | null>(null);
@@ -27,6 +28,10 @@ export default function EmbedderSetup(): React.ReactElement {
27
28
  const [configResult, setConfigResult] = useState<SetupConfigureResponse | null>(null);
28
29
  const [configError, setConfigError] = useState<string | null>(null);
29
30
  const [customUrl, setCustomUrl] = useState("");
31
+ // True once the URL field has been seeded from the persisted endpoint URL —
32
+ // seeding runs only on the first status load that carries the field so the
33
+ // user's in-flight edits are never clobbered by a poll refresh.
34
+ const [customUrlSeeded, setCustomUrlSeeded] = useState(false);
30
35
 
31
36
  const loadStatus = useCallback(() => {
32
37
  fetchSetupStatus()
@@ -36,21 +41,43 @@ export default function EmbedderSetup(): React.ReactElement {
36
41
  );
37
42
  }, []);
38
43
 
39
- const applyEmbedder = useCallback((embedder: "ollama" | "llama" | "trigram" | "custom" | "onnx", url?: string) => {
40
- setConfiguring(embedder);
41
- setConfigError(null);
42
- setConfigResult(null);
43
- configureEmbedder({ embedder, url })
44
- .then((r) => {
45
- setConfigResult(r);
46
- setConfiguring(null);
47
- loadStatus();
44
+ const applyEmbedder = useCallback(
45
+ (
46
+ embedder: "ollama" | "llama" | "trigram" | "custom" | "onnx",
47
+ url?: string,
48
+ apiKey?: string,
49
+ dim?: string,
50
+ headers?: string,
51
+ allowRemote?: boolean,
52
+ ) => {
53
+ setConfiguring(embedder);
54
+ setConfigError(null);
55
+ setConfigResult(null);
56
+ // ENC-1a: optional custom-endpoint URL + bearer key ride on the setup-configure POST (additive; flag-off ignores them).
57
+ // ENC-1b: the dim / headers / allow-remote fields are forwarded additively, gated on the same
58
+ // `embeddingHeadersSet` presence marker the aggregator/server uses (no separate client env read).
59
+ const enc1bOn = "embeddingHeadersSet" in (status ?? {});
60
+ configureEmbedder({
61
+ embedder,
62
+ url,
63
+ embeddingEndpointUrl: url,
64
+ embeddingApiKey: apiKey,
65
+ ...(enc1bOn
66
+ ? { embeddingDim: dim, embeddingHeaders: headers, allowRemoteEmbedder: allowRemote }
67
+ : {}),
48
68
  })
49
- .catch((e: unknown) => {
50
- setConfigError(e instanceof Error ? e.message : String(e));
51
- setConfiguring(null);
52
- });
53
- }, [loadStatus]);
69
+ .then((r) => {
70
+ setConfigResult(r);
71
+ setConfiguring(null);
72
+ loadStatus();
73
+ })
74
+ .catch((e: unknown) => {
75
+ setConfigError(e instanceof Error ? e.message : String(e));
76
+ setConfiguring(null);
77
+ });
78
+ },
79
+ [loadStatus, status],
80
+ );
54
81
 
55
82
  const runDetect = useCallback(() => {
56
83
  setRunningDetect(true);
@@ -70,6 +97,17 @@ export default function EmbedderSetup(): React.ReactElement {
70
97
  loadStatus();
71
98
  }, [loadStatus]);
72
99
 
100
+ // Seed the custom-endpoint URL field once from the persisted
101
+ // `embeddingEndpointUrl` (ENC-1a). Later poll refreshes do NOT re-seed —
102
+ // only the first load, so typed edits survive the 5s status poll.
103
+ useEffect(() => {
104
+ if (customUrlSeeded) return;
105
+ if (status && typeof status.embeddingEndpointUrl === "string" && status.embeddingEndpointUrl.length > 0) {
106
+ setCustomUrl(status.embeddingEndpointUrl);
107
+ setCustomUrlSeeded(true);
108
+ }
109
+ }, [status, customUrlSeeded]);
110
+
73
111
  // Poll status every 5s — the same cadence the Setup Cortex sub-tab uses
74
112
  // (see useSetupCortexPoll) so the embedder + cortex sub-tabs share one poll
75
113
  // contract (VC9D consolidation). Detection is memoized server-side, so the
@@ -88,41 +126,6 @@ export default function EmbedderSetup(): React.ReactElement {
88
126
  return () => clearInterval(id);
89
127
  }, [runDetect]);
90
128
 
91
- const embedderLabel = (e: string): string => {
92
- switch (e) {
93
- case "trigram":
94
- return "TrigramEmbedder (heuristic, default)";
95
- case "http":
96
- return "HTTP Embedder (BYO localhost)";
97
- case "minilm":
98
- return "MiniLM (experimental)";
99
- default:
100
- return "Unknown";
101
- }
102
- };
103
-
104
- const trigramWarning = status?.currentEmbedder === "trigram" && (
105
- <div style={styles.warning}>
106
- <strong>Note:</strong> TrigramEmbedder is a heuristic-strength default
107
- embedder. It works without any setup but recall quality may be lower
108
- than a dedicated embedding backend. If you notice poor recall results,
109
- consider installing Ollama and running the{" "}
110
- <code>/megasetup</code> command, or set{" "}
111
- <code>MEGACOMPACT_EMBEDDING_URL</code> to point at your own localhost
112
- embedding server.
113
- </div>
114
- );
115
-
116
- const detectBadge = (installed: boolean): React.ReactElement => {
117
- const bg = installed ? "#1a5a1a" : "#3a1a1a";
118
- const color = installed ? "#4caf50" : "#f44336";
119
- return (
120
- <span style={{ ...styles.badge, background: bg, color }}>
121
- {installed ? "Detected" : "Not found"}
122
- </span>
123
- );
124
- };
125
-
126
129
  return (
127
130
  <div style={styles.container}>
128
131
  <h2 style={{ fontSize: "1.3rem", marginTop: 0, marginBottom: "1rem" }}>
@@ -132,69 +135,7 @@ export default function EmbedderSetup(): React.ReactElement {
132
135
  <EmbedderHealthCard />
133
136
 
134
137
  {/* Current Config Section */}
135
- <div style={styles.section}>
136
- <h3 style={styles.sectionTitle}>Current Embedder Configuration</h3>
137
- {statusError && (
138
- <p style={{ color: "#f44336", fontSize: "0.85rem" }}>
139
- Error: {statusError}
140
- </p>
141
- )}
142
- {status && (
143
- <>
144
- <div style={styles.row}>
145
- <span style={styles.label}>Active Embedder:</span>
146
- <span style={styles.value}>
147
- {embedderLabel(status.currentEmbedder)}
148
- </span>
149
- </div>
150
- {"configuredEmbedder" in status && status.configuredEmbedder !== status.currentEmbedder && (
151
- <div style={styles.warning}>
152
- <strong>Configured but not active:</strong>{" "}
153
- {embedderLabel(status.configuredEmbedder)} is configured in
154
- .mega-compact.env but not yet loaded.{" "}
155
- <strong>Restart pi</strong> to activate it.
156
- {status.configuredUrl && (
157
- <> ({status.configuredUrl})</>
158
- )}
159
- </div>
160
- )}
161
- {"restartRequired" in status && status.restartRequired && (
162
- <div style={{ ...styles.row, color: "#ff9800" }}>
163
- <span style={styles.label}>Status:</span>
164
- <span style={styles.value}>
165
- Restart required to activate the configured embedder
166
- </span>
167
- </div>
168
- )}
169
- <div style={styles.row}>
170
- <span style={styles.label}>Embedding URL:</span>
171
- <span style={styles.value}>
172
- {status.embeddingUrl ?? (
173
- <span style={{ color: "#888" }}>not set</span>
174
- )}
175
- </span>
176
- </div>
177
- <div style={styles.row}>
178
- <span style={styles.label}>Embed Cache:</span>
179
- <span style={styles.value}>
180
- {status.embedCache ?? (
181
- <span style={{ color: "#888" }}>not set</span>
182
- )}
183
- </span>
184
- </div>
185
- <div style={styles.row}>
186
- <span style={styles.label}>MiniLM:</span>
187
- <span style={styles.value}>{status.minilm ? "enabled" : "disabled"}</span>
188
- </div>
189
- {trigramWarning}
190
- </>
191
- )}
192
- {!status && !statusError && (
193
- <p style={{ color: "#888", fontSize: "0.85rem" }}>
194
- Loading configuration...
195
- </p>
196
- )}
197
- </div>
138
+ <CurrentConfigSection status={status} statusError={statusError} />
198
139
 
199
140
  {/* Detection Section */}
200
141
  <div style={styles.section}>
@@ -334,7 +275,7 @@ export default function EmbedderSetup(): React.ReactElement {
334
275
  {configuring === "trigram" ? "Writing..." : "Use Trigram (default)"}
335
276
  </button>
336
277
  </div>
337
- {configResult && (
278
+ {configResult && configResult.embedder !== "custom" && (
338
279
  <div style={styles.warning}>
339
280
  {configResult.alreadyActive
340
281
  ? "Already active — no restart needed."
@@ -356,6 +297,13 @@ export default function EmbedderSetup(): React.ReactElement {
356
297
  setCustomUrl={setCustomUrl}
357
298
  applyEmbedder={applyEmbedder}
358
299
  configuring={configuring}
300
+ inputEnabled={status !== null && "embeddingApiKeySet" in status}
301
+ apiKeySet={status?.embeddingApiKeySet === true}
302
+ embeddingDim={status?.embeddingDim}
303
+ embeddingHeadersSet={status?.embeddingHeadersSet === true}
304
+ allowRemoteEmbedder={status?.allowRemoteEmbedder}
305
+ configureResult={configResult}
306
+ configureError={configError}
359
307
  />
360
308
 
361
309
  {/* Help Section */}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * SetupTab/EmbedderSetupHelpers.tsx — extracted render helpers for EmbedderSetup.
3
+ *
4
+ * Split from EmbedderSetup.tsx to respect the extensions/ 400-line soft limit
5
+ * (ENC-1a pushed the embedder card over its cap, so the helpers were extracted).
6
+ */
7
+ import type React from "react";
8
+ import type { SetupStatusResponse } from "@contracts";
9
+ import { styles } from "./EmbedderSetupStyles";
10
+
11
+ /** Human label for an embedder identifier. */
12
+ export function embedderLabel(e: string): string {
13
+ switch (e) {
14
+ case "trigram":
15
+ return "TrigramEmbedder (heuristic, default)";
16
+ case "http":
17
+ return "HTTP Embedder (BYO localhost)";
18
+ case "minilm":
19
+ return "MiniLM (experimental)";
20
+ default:
21
+ return "Unknown";
22
+ }
23
+ }
24
+
25
+ /** Warn when the active embedder is the heuristic trigram default. */
26
+ export function trigramWarning(status: SetupStatusResponse | null): React.ReactElement | null {
27
+ if (status?.currentEmbedder !== "trigram") return null;
28
+ return (
29
+ <div style={styles.warning}>
30
+ <strong>Note:</strong> TrigramEmbedder is a heuristic-strength default
31
+ embedder. It works without any setup but recall quality may be lower
32
+ than a dedicated embedding backend. If you notice poor recall results,
33
+ consider installing Ollama and running the{" "}
34
+ <code>/megasetup</code> command, or set{" "}
35
+ <code>MEGACOMPACT_EMBEDDING_URL</code> to point at your own localhost
36
+ embedding server.
37
+ </div>
38
+ );
39
+ }
40
+
41
+ /** Installed / not-found badge dot for a detection row. */
42
+ export function detectBadge(installed: boolean): React.ReactElement {
43
+ const bg = installed ? "#1a5a1a" : "#3a1a1a";
44
+ const color = installed ? "#4caf50" : "#f44336";
45
+ return (
46
+ <span style={{ ...styles.badge, background: bg, color }}>
47
+ {installed ? "Detected" : "Not found"}
48
+ </span>
49
+ );
50
+ }
51
+
52
+ /** The "Current Embedder Configuration" readout block. */
53
+ export function CurrentConfigSection({
54
+ status,
55
+ statusError,
56
+ }: {
57
+ status: SetupStatusResponse | null;
58
+ statusError: string | null;
59
+ }): React.ReactElement {
60
+ return (
61
+ <div style={styles.section}>
62
+ <h3 style={styles.sectionTitle}>Current Embedder Configuration</h3>
63
+ {statusError && (
64
+ <p style={{ color: "#f44336", fontSize: "0.85rem" }}>
65
+ Error: {statusError}
66
+ </p>
67
+ )}
68
+ {status && (
69
+ <>
70
+ <div style={styles.row}>
71
+ <span style={styles.label}>Active Embedder:</span>
72
+ <span style={styles.value}>
73
+ {embedderLabel(status.currentEmbedder)}
74
+ </span>
75
+ </div>
76
+ {"configuredEmbedder" in status && status.configuredEmbedder !== status.currentEmbedder && (
77
+ <div style={styles.warning}>
78
+ <strong>Configured but not active:</strong>{" "}
79
+ {embedderLabel(status.configuredEmbedder)} is configured in
80
+ .mega-compact.env but not yet loaded.{" "}
81
+ <strong>Restart pi</strong> to activate it.
82
+ {status.configuredUrl && (
83
+ <> ({status.configuredUrl})</>
84
+ )}
85
+ </div>
86
+ )}
87
+ {"restartRequired" in status && status.restartRequired && (
88
+ <div style={{ ...styles.row, color: "#ff9800" }}>
89
+ <span style={styles.label}>Status:</span>
90
+ <span style={styles.value}>
91
+ Restart required to activate the configured embedder
92
+ </span>
93
+ </div>
94
+ )}
95
+ <div style={styles.row}>
96
+ <span style={styles.label}>Embedding URL:</span>
97
+ <span style={styles.value}>
98
+ {status.embeddingUrl ?? (
99
+ <span style={{ color: "#888" }}>not set</span>
100
+ )}
101
+ </span>
102
+ </div>
103
+ <div style={styles.row}>
104
+ <span style={styles.label}>Embed Cache:</span>
105
+ <span style={styles.value}>
106
+ {status.embedCache ?? (
107
+ <span style={{ color: "#888" }}>not set</span>
108
+ )}
109
+ </span>
110
+ </div>
111
+ <div style={styles.row}>
112
+ <span style={styles.label}>MiniLM:</span>
113
+ <span style={styles.value}>{status.minilm ? "enabled" : "disabled"}</span>
114
+ </div>
115
+ {trigramWarning(status)}
116
+ </>
117
+ )}
118
+ {!status && !statusError && (
119
+ <p style={{ color: "#888", fontSize: "0.85rem" }}>
120
+ Loading configuration...
121
+ </p>
122
+ )}
123
+ </div>
124
+ );
125
+ }
@@ -27,6 +27,34 @@ export interface SetupStatusResponse {
27
27
  readonly embedCache: string | null;
28
28
  /** Whether MEGACOMPACT_MINILM is set to a truthy value. */
29
29
  readonly minilm: boolean;
30
+ /** ENC-1a: the external embedder endpoint URL persisted in the per-repo
31
+ * `.mega-compact.env`, when one is configured (additive, flag-gated). */
32
+ readonly embeddingEndpointUrl?: string;
33
+ /** ENC-1a: whether an external embedder API key is configured. The raw key is
34
+ * NEVER returned — only this boolean presence marker (additive, flag-gated). */
35
+ readonly embeddingApiKeySet?: boolean;
36
+ /** ENC-1b: the persisted `MEGACOMPACT_EMBEDDING_DIM` value as its numeric
37
+ * string, when one is configured (additive, flag-gated). */
38
+ readonly embeddingDim?: string;
39
+ /** ENC-1b: whether `MEGACOMPACT_EMBEDDING_HEADERS` is configured. The raw
40
+ * headers JSON is NEVER returned — only this boolean presence marker
41
+ * (additive, flag-gated, redaction mirrors embeddingApiKeySet). */
42
+ readonly embeddingHeadersSet?: boolean;
43
+ /** ENC-1b: whether `MEGACOMPACT_ALLOW_REMOTE_EMBEDDER` is set (default off —
44
+ * a deliberate escape hatch that skips the loopback-only check; additive,
45
+ * flag-gated). */
46
+ readonly allowRemoteEmbedder?: boolean;
47
+ /** ENC-1b: whether `MEGACOMPACT_ENCODER_NATIVE=1` is set, i.e. the operator
48
+ * opted into the onnxruntime-node native backend (additive, flag-gated). */
49
+ readonly encoderNativeOptIn?: boolean;
50
+ /** ENC-1b: the current effective ONNX runtime backend, computed live through
51
+ * the existing `selectRuntimeBackend`. `modeB` (flag-off / no runtime) is
52
+ * projected to `wasm` for display (additive, flag-gated). */
53
+ readonly encoderBackend?: "wasm" | "native";
54
+ /** ENC-1b: the runtime's own demotion reason when the effective backend
55
+ * falls back (e.g. native selected but onnxruntime-node absent / darwin-x64
56
+ * demoted), surfaced verbatim; null when no demotion (additive, flag-gated). */
57
+ readonly encoderDemotionReason?: string | null;
30
58
  }
31
59
 
32
60
  /**
@@ -72,6 +100,25 @@ export interface SetupConfigureRequest {
72
100
  readonly embedder: "ollama" | "llama" | "trigram" | "custom" | "onnx";
73
101
  /** Override URL (required for "custom"; optional defaults for ollama/llama/onnx). */
74
102
  readonly url?: string;
103
+ /** ENC-1a: external embedder endpoint URL, persisted to the per-repo
104
+ * `.mega-compact.env` (additive, flag-gated). */
105
+ readonly embeddingEndpointUrl?: string;
106
+ /** ENC-1a: external embedder API key, persisted to the per-repo
107
+ * `.mega-compact.env` only — never returned by any GET (additive, flag-gated). */
108
+ readonly embeddingApiKey?: string;
109
+ /** ENC-1b: persisted `MEGACOMPACT_EMBEDDING_DIM` as a positive integer string
110
+ * within `ENC_1B_MAX_EMBEDDING_DIM` (validated; additive, flag-gated). */
111
+ readonly embeddingDim?: string;
112
+ /** ENC-1b: persisted `MEGACOMPACT_EMBEDDING_HEADERS` JSON object string
113
+ * (secret-bearing — written verbatim, NEVER echoed back; validated to parse
114
+ * as a JSON object; additive, flag-gated). */
115
+ readonly embeddingHeaders?: string;
116
+ /** ENC-1b: opt in to `MEGACOMPACT_ALLOW_REMOTE_EMBEDDER` (default off — a
117
+ * deliberate escape hatch; additive, flag-gated). */
118
+ readonly allowRemoteEmbedder?: boolean;
119
+ /** ENC-1b: opt in to `MEGACOMPACT_ENCODER_NATIVE` (onnxruntime-node native
120
+ * backend; additive, flag-gated). */
121
+ readonly encoderNativeOptIn?: boolean;
75
122
  }
76
123
 
77
124
  /** Response for POST /api/setup-configure. */
@@ -294,5 +294,17 @@ export const VECTOR_CORTEX_SETTINGS: SettingGroup = {
294
294
  "ENC-0g makes the Setup Cortex status route honest: reads the ENC-0f QualificationV1 record (when ENC_0F is ON and a record exists) and lets its verdict override the structural verify for the qualification field; computes the blocker list from (platform, record, manifest head-count) with corrected HG statuses/wording; re-derives VC9B action gating from live blockers. OFF = verdict from verifyEncoderAsset alone, static SETUP_CORTEX_BLOCKERS, static action gating (byte-identical ENC-0f predecessor).",
295
295
  true,
296
296
  ),
297
+ boolDirect(
298
+ "MEGACOMPACT_ENC_1A",
299
+ "ENC-1a external embedder API key + endpoint Settings fields",
300
+ "ENC-1a adds a Settings-visible field pair for the external embedder endpoint URL + optional Bearer API key, persisted by the setup-configure writer branch to the per-repo .mega-compact.env (MEGACOMPACT_EMBEDDING_URL / MEGACOMPACT_EMBEDDING_KEY). The GET status route echoes the endpoint URL and reports embeddingApiKeySet as a boolean ONLY — the raw key is never returned. OFF = no new GET fields, no writer branch, no new Settings text rows (byte-identical ENC-0g predecessor).",
301
+ true,
302
+ ),
303
+ boolDirect(
304
+ "MEGACOMPACT_ENC_1B",
305
+ "ENC-1b ONNX runtime backend + embedder API settings",
306
+ "ENC-1b completes the embedder API Settings surface (dim / headers / allow-remote persisted to the per-repo .mega-compact.env) and surfaces the ONNX runtime selection knob (MEGACOMPACT_ENCODER_NATIVE) as a toggle plus the current effective backend + demotion reason. OFF = no new GET fields, no writer branch, no new Settings rows or toggle (byte-identical ENC-1a predecessor).",
307
+ true,
308
+ ),
297
309
  ],
298
310
  };