@oxy-hq/sdk 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/shell.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // @oxy/sdk - TypeScript SDK for Oxy data platform
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3
- const require_react = require('./react-B7Blpmt7.cjs');
3
+ const require_react = require('./react-BeweGSYF.cjs');
4
4
  let react = require("react");
5
5
  react = require_react.__toESM(react, 1);
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
@@ -1189,6 +1189,38 @@ function HistoryIcon() {
1189
1189
  ]
1190
1190
  });
1191
1191
  }
1192
+ function SearchIcon() {
1193
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1194
+ width: "14",
1195
+ height: "14",
1196
+ viewBox: "0 0 24 24",
1197
+ fill: "none",
1198
+ stroke: "currentColor",
1199
+ strokeWidth: "2",
1200
+ strokeLinecap: "round",
1201
+ strokeLinejoin: "round",
1202
+ "aria-hidden": "true",
1203
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1204
+ cx: "11",
1205
+ cy: "11",
1206
+ r: "8"
1207
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m21 21-4.3-4.3" })]
1208
+ });
1209
+ }
1210
+ function ThreadIcon() {
1211
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1212
+ width: "15",
1213
+ height: "15",
1214
+ viewBox: "0 0 24 24",
1215
+ fill: "none",
1216
+ stroke: "currentColor",
1217
+ strokeWidth: "2",
1218
+ strokeLinecap: "round",
1219
+ strokeLinejoin: "round",
1220
+ "aria-hidden": "true",
1221
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14 9a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" })]
1222
+ });
1223
+ }
1192
1224
  function ExternalIcon() {
1193
1225
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1194
1226
  width: "14",
@@ -1240,6 +1272,26 @@ function StopIcon() {
1240
1272
  function chartsFrom(events) {
1241
1273
  return events.filter((ev) => ev.type === "chart_rendered").map((ev) => ev.data).filter((b) => b?.config && Array.isArray(b.columns) && Array.isArray(b.rows));
1242
1274
  }
1275
+ /** History label: a relative "N minutes/hours/days ago" for when the
1276
+ * conversation was started. */
1277
+ function formatHistoryTime(iso) {
1278
+ const d = new Date(iso);
1279
+ if (Number.isNaN(d.getTime())) return "";
1280
+ const s = Math.max(0, Math.round((Date.now() - d.getTime()) / 1e3));
1281
+ if (s < 45) return "just now";
1282
+ const min = Math.round(s / 60);
1283
+ if (min < 60) return `${min} minute${min === 1 ? "" : "s"} ago`;
1284
+ const hr = Math.round(s / 3600);
1285
+ if (hr < 24) return `${hr} hour${hr === 1 ? "" : "s"} ago`;
1286
+ const day = Math.round(s / 86400);
1287
+ if (day < 30) return `${day} day${day === 1 ? "" : "s"} ago`;
1288
+ const mo = Math.round(day / 30);
1289
+ if (mo < 12) return `${mo} month${mo === 1 ? "" : "s"} ago`;
1290
+ const yr = Math.round(day / 365);
1291
+ return `${yr} year${yr === 1 ? "" : "s"} ago`;
1292
+ }
1293
+ /** How many history rows to show before the "Show more" button. */
1294
+ const HISTORY_PAGE_SIZE = 12;
1243
1295
  /** Rebuild a completed turn from a transcript turn's processed events —
1244
1296
  * identical to what the live dock accumulates during a run. */
1245
1297
  function turnFromEvents(question, events) {
@@ -1265,9 +1317,12 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1265
1317
  const [threadId, setThreadId] = react.useState(null);
1266
1318
  const [history, setHistory] = react.useState([]);
1267
1319
  const [historyOpen, setHistoryOpen] = react.useState(false);
1320
+ const [historyQuery, setHistoryQuery] = react.useState("");
1321
+ const [historyShown, setHistoryShown] = react.useState(HISTORY_PAGE_SIZE);
1268
1322
  const [restoring, setRestoring] = react.useState(false);
1269
1323
  const scrollRef = react.useRef(null);
1270
1324
  const restoreGenRef = react.useRef(0);
1325
+ const startedAtRef = react.useRef(null);
1271
1326
  const busy = run.state === "running";
1272
1327
  const liveSteps = react.useMemo(() => buildTraceSteps(run.events), [run.events]);
1273
1328
  const liveLlm = react.useMemo(() => aggregateLlmStats(run.events), [run.events]);
@@ -1291,7 +1346,8 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1291
1346
  id: threadId ?? all[0].question,
1292
1347
  title: all[0].question,
1293
1348
  turns: all,
1294
- threadId
1349
+ threadId,
1350
+ createdAt: startedAtRef.current ?? (/* @__PURE__ */ new Date()).toISOString()
1295
1351
  };
1296
1352
  };
1297
1353
  const resetToEmpty = () => {
@@ -1301,6 +1357,7 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1301
1357
  setPending(null);
1302
1358
  setDraft("");
1303
1359
  setThreadId(null);
1360
+ startedAtRef.current = null;
1304
1361
  };
1305
1362
  const newChat = () => {
1306
1363
  const snap = snapshotCurrent();
@@ -1334,6 +1391,7 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1334
1391
  setPending(null);
1335
1392
  setDraft("");
1336
1393
  setThreadId(id);
1394
+ startedAtRef.current = serverThreads.find((t) => t.id === id)?.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
1337
1395
  setHistoryOpen(false);
1338
1396
  setRestoring(true);
1339
1397
  try {
@@ -1348,6 +1406,7 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1348
1406
  const question = raw.trim();
1349
1407
  if (!question || busy) return;
1350
1408
  restoreGenRef.current += 1;
1409
+ if (startedAtRef.current === null) startedAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
1351
1410
  if (pending !== null) {
1352
1411
  const finalized = finalizedPendingTurn();
1353
1412
  if (finalized) setTurns((t) => [...t, finalized]);
@@ -1363,16 +1422,31 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1363
1422
  });
1364
1423
  const empty = pending === null && turns.length === 0;
1365
1424
  const canNewChat = !empty;
1366
- const historyEntries = [...history.filter((c) => c.id !== threadId).map((c) => ({
1425
+ const currentId = threadId ?? turns[0]?.question ?? pending ?? null;
1426
+ const historyEntries = [...history.map((c) => ({
1367
1427
  id: c.id,
1368
1428
  title: c.title,
1369
- meta: `${c.turns.length} message${c.turns.length === 1 ? "" : "s"}`,
1370
- onOpen: () => openConversation(c)
1371
- })), ...serverThreads.filter((t) => t.id !== threadId && !history.some((c) => c.id === t.id)).map((t) => ({
1429
+ createdAt: c.createdAt,
1430
+ active: c.id === currentId,
1431
+ onOpen: c.id === currentId ? () => setHistoryOpen(false) : () => openConversation(c)
1432
+ })), ...serverThreads.filter((t) => !history.some((c) => c.id === t.id)).map((t) => ({
1372
1433
  id: t.id,
1373
1434
  title: t.title,
1374
- onOpen: () => void openServerThread(t.id)
1435
+ createdAt: t.created_at,
1436
+ active: t.id === currentId,
1437
+ onOpen: t.id === currentId ? () => setHistoryOpen(false) : () => void openServerThread(t.id)
1375
1438
  }))];
1439
+ if (!empty && currentId != null && !historyEntries.some((e) => e.id === currentId)) historyEntries.push({
1440
+ id: currentId,
1441
+ title: turns[0]?.question ?? pending ?? currentId,
1442
+ createdAt: startedAtRef.current ?? (/* @__PURE__ */ new Date()).toISOString(),
1443
+ active: true,
1444
+ onOpen: () => setHistoryOpen(false)
1445
+ });
1446
+ historyEntries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1447
+ const q = historyQuery.trim().toLowerCase();
1448
+ const filteredEntries = q ? historyEntries.filter((e) => e.title.toLowerCase().includes(q)) : historyEntries;
1449
+ const visibleEntries = filteredEntries.slice(0, historyShown);
1376
1450
  const placeholder = run.state === "needs_clarification" ? "Reply…" : pending !== null ? "Ask a follow-up…" : `Ask Oxygen anything about ${workspaceName ?? "your workspace"}…`;
1377
1451
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
1378
1452
  className: cx("oxy-shell-scope oxy-askdock", !open && "oxy-askdock--closed", className),
@@ -1405,7 +1479,11 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1405
1479
  onClick: () => {
1406
1480
  const next = !historyOpen;
1407
1481
  setHistoryOpen(next);
1408
- if (next) refetchHistory();
1482
+ if (next) {
1483
+ refetchHistory();
1484
+ setHistoryQuery("");
1485
+ setHistoryShown(HISTORY_PAGE_SIZE);
1486
+ }
1409
1487
  },
1410
1488
  "data-testid": "askdock-history",
1411
1489
  "aria-label": "Chat history",
@@ -1430,24 +1508,51 @@ function AskDock({ agentId, workspaceName, suggestions = [], threadsHref, open,
1430
1508
  ]
1431
1509
  })
1432
1510
  ]
1433
- }), historyOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1511
+ }), historyOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1434
1512
  className: "oxy-askdock__history",
1435
1513
  "data-testid": "askdock-history-panel",
1436
- children: historyEntries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1514
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1515
+ className: "oxy-askdock__history-search",
1516
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1517
+ type: "text",
1518
+ className: "oxy-askdock__history-searchinput",
1519
+ placeholder: "Search threads…",
1520
+ value: historyQuery,
1521
+ onChange: (e) => {
1522
+ setHistoryQuery(e.target.value);
1523
+ setHistoryShown(HISTORY_PAGE_SIZE);
1524
+ },
1525
+ "aria-label": "Search threads"
1526
+ })]
1527
+ }), visibleEntries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1437
1528
  className: "oxy-askdock__history-empty",
1438
- children: "No previous chats yet."
1439
- }) : historyEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1529
+ children: historyQuery ? "No matching chats." : "No previous chats yet."
1530
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [visibleEntries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1440
1531
  type: "button",
1441
- className: "oxy-askdock__history-item",
1532
+ className: cx("oxy-askdock__history-item", entry.active && "oxy-askdock__history-item--active"),
1442
1533
  onClick: entry.onOpen,
1443
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1444
- className: "oxy-askdock__history-title",
1445
- children: entry.title
1446
- }), entry.meta && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1447
- className: "oxy-askdock__history-meta",
1448
- children: entry.meta
1449
- })]
1450
- }, entry.id))
1534
+ title: entry.title,
1535
+ "aria-current": entry.active ? "true" : void 0,
1536
+ children: [
1537
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1538
+ className: "oxy-askdock__history-icon",
1539
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThreadIcon, {})
1540
+ }),
1541
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1542
+ className: "oxy-askdock__history-title",
1543
+ children: entry.title
1544
+ }),
1545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1546
+ className: "oxy-askdock__history-time",
1547
+ children: formatHistoryTime(entry.createdAt)
1548
+ })
1549
+ ]
1550
+ }, entry.id)), filteredEntries.length > visibleEntries.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1551
+ type: "button",
1552
+ className: "oxy-askdock__history-more",
1553
+ onClick: () => setHistoryShown((n) => n + HISTORY_PAGE_SIZE),
1554
+ children: "Show more"
1555
+ })] })]
1451
1556
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1452
1557
  className: "oxy-askdock__scroll",
1453
1558
  ref: scrollRef,
@@ -1907,25 +2012,6 @@ function MessagesIcon() {
1907
2012
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M16 10a2 2 0 0 1-2 2H6l-4 4V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M20 9a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1" })]
1908
2013
  });
1909
2014
  }
1910
- /** Settings glyph (lucide "settings" outline, inlined). */
1911
- function GearIcon() {
1912
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1913
- width: "16",
1914
- height: "16",
1915
- viewBox: "0 0 24 24",
1916
- fill: "none",
1917
- stroke: "currentColor",
1918
- strokeWidth: "2",
1919
- strokeLinecap: "round",
1920
- strokeLinejoin: "round",
1921
- "aria-hidden": "true",
1922
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
1923
- cx: "12",
1924
- cy: "12",
1925
- r: "3"
1926
- })]
1927
- });
1928
- }
1929
2015
  /** Rewrite a shell-context relative product path to an absolute URL against
1930
2016
  * the oxy origin, when one is configured. `/api/*` paths are left relative
1931
2017
  * — they ride the host's same-origin fetch / dev proxy (which attaches
@@ -1968,8 +2054,15 @@ function buildRailGroups(data, currentAppSlug, abs) {
1968
2054
  * The workspace chrome around a customer app: icon rail + universal top bar
1969
2055
  * + content column — visually identical to the main web-app shell.
1970
2056
  */
1971
- function OxyShell({ children, pageLabel, topBarLeft, topBarExtra, railBottom, hideTopBar, askHotkey = true, productBaseUrl, className }) {
2057
+ function OxyShell({ children, pageLabel, topBarLeft, topBarExtra, railBottom, hideTopBar, askHotkey = true, productBaseUrl, className, chromeBackground, chromeForeground }) {
1972
2058
  const abs = makeAbs(productBaseUrl);
2059
+ const chromeStyle = {
2060
+ ...chromeBackground ? { "--sidebar-background": chromeBackground } : {},
2061
+ ...chromeForeground ? {
2062
+ "--foreground": chromeForeground,
2063
+ "--muted-foreground": `color-mix(in srgb, ${chromeForeground} 65%, transparent)`
2064
+ } : {}
2065
+ };
1973
2066
  const { appSlug } = require_react.useOxyApp();
1974
2067
  const { manifest } = require_react.useResolvedManifest();
1975
2068
  const { data, loading } = useShellContext();
@@ -1992,22 +2085,13 @@ function OxyShell({ children, pageLabel, topBarLeft, topBarExtra, railBottom, hi
1992
2085
  window.addEventListener("keydown", onKey);
1993
2086
  return () => window.removeEventListener("keydown", onKey);
1994
2087
  }, [askAgent, askHotkey]);
1995
- const settingsEntry = data?.links.settings ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellTooltip, {
1996
- content: "Settings",
1997
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1998
- href: abs(data.links.settings),
1999
- "aria-label": "Settings",
2000
- "data-testid": "rail-settings",
2001
- className: "oxy-rail__item",
2002
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {})
2003
- })
2004
- }) : null;
2005
- const bottom = railBottom || settingsEntry ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [railBottom, settingsEntry] }) : void 0;
2088
+ const bottom = railBottom;
2006
2089
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellPortalContext.Provider, {
2007
2090
  value: container,
2008
2091
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2009
2092
  ref: setContainer,
2010
2093
  className: cx("oxy-shell-scope oxy-shell", degraded && "oxy-shell--degraded", className),
2094
+ style: chromeStyle,
2011
2095
  children: [
2012
2096
  !degraded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShellRail, {
2013
2097
  top: data ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceTile, {