research-agent-ui 0.1.293 → 0.1.294

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 (54) hide show
  1. package/README.md +42 -1
  2. package/dist/ArticleExtractPanel-25tRTnpl.cjs +2 -0
  3. package/dist/ArticleExtractPanel-D_y16YHG.js +389 -0
  4. package/dist/ChatConversationThread-BIJ8aNug.js +1428 -0
  5. package/dist/ChatConversationThread-BoNH0wvs.cjs +17 -0
  6. package/dist/DownloadsDialog-CCPj0hA8.cjs +1 -0
  7. package/dist/{DownloadsDialog-bTEDe2DI.js → DownloadsDialog-DvS23Dh-.js} +8 -9
  8. package/dist/api/handlers/autocomplete.d.ts +6 -9
  9. package/dist/api/handlers/openai-transcription.d.ts +53 -0
  10. package/dist/api/handlers/transcript.d.ts +33 -0
  11. package/dist/api/index.d.ts +1 -0
  12. package/dist/api.cjs +10 -10
  13. package/dist/api.mjs +161 -208
  14. package/dist/app/index.d.ts +1 -1
  15. package/dist/app/useChunkErrorReload.d.ts +13 -2
  16. package/dist/components/ArticleReader/ArticleReaderMount.d.ts +5 -0
  17. package/dist/components/ArticleReader/ExtractPanelContext.d.ts +17 -1
  18. package/dist/components/ChatConversation/MessageBubble/AssistantMessageActions.d.ts +2 -1
  19. package/dist/components/ChatConversation/background-art.d.ts +3 -1
  20. package/dist/components/ChatConversation/background-cache.d.ts +41 -0
  21. package/dist/components/Footer.d.ts +5 -4
  22. package/dist/components/Footer.stories.d.ts +3 -3
  23. package/dist/config.cjs +1 -1
  24. package/dist/config.d.ts +9 -0
  25. package/dist/config.mjs +4 -3
  26. package/dist/dialog-7GDz0sWx.cjs +1 -0
  27. package/dist/dialog-Cufn2u50.js +80 -0
  28. package/dist/hooks/voice/useKokoroTTS.d.ts +2 -0
  29. package/dist/hooks/voice/useTextToVoice.d.ts +2 -1
  30. package/dist/index.cjs +1 -1
  31. package/dist/index.d.ts +5 -1
  32. package/dist/index.mjs +4 -2
  33. package/dist/lib/file-sources.d.ts +8 -0
  34. package/dist/lib/google-drive-source.d.ts +100 -0
  35. package/dist/lib/kokoro.d.ts +14 -2
  36. package/dist/lib/qwksearch-connect.d.ts +79 -0
  37. package/dist/settings/index.d.ts +15 -0
  38. package/dist/src-GPr1ceY8.js +6165 -0
  39. package/dist/src-ebmQu_if.cjs +34 -0
  40. package/dist/tooltip-Bl3IdX-I.js +179 -0
  41. package/dist/tooltip-DmUO6ZUz.cjs +1 -0
  42. package/dist/types/fileSource.d.ts +9 -0
  43. package/dist/workspace/QwkSearchWorkspaceApp.d.ts +9 -2
  44. package/dist/workspace/ResearchWorkspaceView.d.ts +18 -4
  45. package/dist/workspace.cjs +1 -1
  46. package/dist/workspace.d.ts +1 -1
  47. package/dist/workspace.mjs +234 -91
  48. package/package.json +54 -51
  49. package/src/app/useChunkErrorReload.ts +92 -0
  50. package/src/settings/index.ts +23 -0
  51. package/src/settings/search.json +19 -1
  52. package/dist/DownloadsDialog-Ca6aRRcP.cjs +0 -1
  53. package/dist/src-HL9JjPc-.js +0 -8218
  54. package/dist/src-r4BsCIB4.cjs +0 -51
@@ -1,25 +1,24 @@
1
1
  "use client";
2
2
  "use client";
3
- import { configureResearchAgentUI as e, researchAgentUIConfig as t } from "./config.mjs";
4
- import { A as n, C as r, D as i, E as a, F as o, G as s, I as c, K as l, L as u, M as d, N as f, O as p, P as m, R as h, S as g, T as _, U as v, W as y, _ as b, a as x, b as S, c as C, d as w, f as T, g as E, h as D, i as O, j as k, k as A, l as j, m as M, n as N, o as P, p as F, q as I, r as L, s as ee, t as R, u as z, v as B, w as te, x as ne, y as re } from "./src-HL9JjPc-.js";
5
- import { useCallback as V, useEffect as H, useRef as ie, useState as U } from "react";
6
- import { useSearchParams as ae } from "next/navigation";
7
- import { jsx as W } from "react/jsx-runtime";
8
- import G from "grab-url";
9
- import { ReasonDocs as K } from "react-reason-editor/reason-docs";
10
- import { themeActions as q } from "react-reason-editor/theme";
11
- import { localeActions as oe } from "react-reason-editor/locale-bundle";
12
- import { Sidebar as J, SidebarContent as Y } from "react-reason-editor-sidebar";
13
- import "katex/dist/katex.min.css";
14
- import "easydrawer/styles.css";
15
- import "katex/contrib/mhchem";
16
- //#region src/workspace/page-tips.ts
17
- function X(e) {
3
+ import { $ as e, A as t, B as n, C as r, D as i, E as a, F as o, H as s, I as c, L as l, M as u, N as d, O as f, P as p, R as m, S as h, T as g, U as _, _ as v, a as y, b, c as x, d as S, et as C, f as w, g as T, h as E, i as D, j as O, k, l as ee, m as te, n as ne, o as A, p as j, r as M, s as N, t as P, u as F, v as I, w as re, x as ie, y as ae, z as oe } from "./src-GPr1ceY8.js";
4
+ import { configureResearchAgentUI as L, researchAgentUIConfig as se } from "./config.mjs";
5
+ import { a as ce, c as R, i as le, o as ue, s as de, u as z } from "./tooltip-Bl3IdX-I.js";
6
+ import { c as fe, o as pe, s as me } from "./dialog-Cufn2u50.js";
7
+ import { useCallback as B, useEffect as V, useRef as he, useState as H } from "react";
8
+ import { useSearchParams as ge } from "next/navigation";
9
+ import { jsx as U } from "react/jsx-runtime";
10
+ import W from "grab-url";
11
+ import { ReasonDocs as G } from "react-reason-editor/reason-docs";
12
+ import { themeActions as _e } from "react-reason-editor/theme";
13
+ import { localeActions as ve } from "react-reason-editor/locale-bundle";
14
+ import { Sidebar as ye, SidebarContent as be } from "react-reason-editor-sidebar";
15
+ // src/workspace/page-tips.ts
16
+ function K(e) {
18
17
  return e.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, " ").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
19
18
  }
20
- var Z = async (e, t) => {
19
+ var q = async (e, t) => {
21
20
  try {
22
- let n = await G("agent/page-tips", {
21
+ let n = await W("agent/page-tips", {
23
22
  method: "POST",
24
23
  body: JSON.stringify({
25
24
  title: e,
@@ -30,9 +29,9 @@ var Z = async (e, t) => {
30
29
  } catch {
31
30
  return [];
32
31
  }
33
- }, Q = async (e, t) => {
32
+ }, J = async (e, t) => {
34
33
  try {
35
- let n = await G("agent/topic-searches", {
34
+ let n = await W("agent/topic-searches", {
36
35
  method: "POST",
37
36
  body: JSON.stringify({
38
37
  title: e,
@@ -43,95 +42,239 @@ var Z = async (e, t) => {
43
42
  } catch {
44
43
  return [];
45
44
  }
45
+ }, Y = "/api/doc/google-docs", X = "application/vnd.google-apps.document", Z = "application/vnd.google-apps.spreadsheet", xe = "application/vnd.google-apps.presentation", Se = "application/vnd.google-apps.drawing", Q = "application/vnd.google-apps.folder", Ce = class extends Error {
46
+ needsReauth;
47
+ needsFolderAccess;
48
+ status;
49
+ constructor(e, t, n = {}) {
50
+ super(e), this.name = "GoogleDriveError", this.status = t, this.needsReauth = !!n.needsReauth, this.needsFolderAccess = !!n.needsFolderAccess;
51
+ }
52
+ };
53
+ async function we(e) {
54
+ let t = await fetch(`${Y}${e}`, { credentials: "same-origin" }), n = {};
55
+ try {
56
+ n = await t.json();
57
+ } catch {}
58
+ if (!t.ok || n?.success === !1) throw new Ce(n?.error || `Google Drive request failed (${t.status})`, t.status, n);
59
+ return n;
60
+ }
61
+ function Te(e) {
62
+ return e === X || e === "text/plain" || e === "text/markdown" ? "editor" : e === "application/pdf" || e === Z || e === xe ? "pdf" : e === Se || e.startsWith("image/") ? "image" : "unsupported";
63
+ }
64
+ function Ee(e) {
65
+ if (e.mimeType === X) return "docx";
66
+ if (e.mimeType === Z) return "xlsx";
67
+ if (e.mimeType === xe) return "pptx";
68
+ if (e.mimeType === Se) return "png";
69
+ let t = e.name.lastIndexOf(".");
70
+ if (t > 0 && t < e.name.length - 1) return e.name.slice(t + 1).toLowerCase();
71
+ if (e.mimeType === "application/pdf") return "pdf";
72
+ let n = e.mimeType.split("/")[1];
73
+ return n && /^[a-z0-9]{2,5}$/.test(n) ? n : void 0;
74
+ }
75
+ var $ = (e, t) => `${e}:${t}`, De = (e) => `${Y}/content?fileId=${encodeURIComponent(e)}`;
76
+ function Oe(e, t) {
77
+ let n = [], r = /* @__PURE__ */ new Set(), i = (e, t) => Number(t.mimeType === "application/vnd.google-apps.folder") - Number(e.mimeType === "application/vnd.google-apps.folder") || e.name.localeCompare(t.name, void 0, {
78
+ numeric: !0,
79
+ sensitivity: "base"
80
+ });
81
+ for (let { root: a, items: o } of t) {
82
+ let s = $(e, a.id);
83
+ if (r.has(s)) continue;
84
+ r.add(s), n.push({
85
+ id: s,
86
+ title: a.name,
87
+ content: "",
88
+ parentId: null,
89
+ isFolder: !0,
90
+ isExpanded: t.length === 1,
91
+ external: {
92
+ sourceId: e,
93
+ fileId: a.id,
94
+ mimeType: Q,
95
+ view: "unsupported",
96
+ webViewLink: a.webViewLink
97
+ }
98
+ });
99
+ let c = /* @__PURE__ */ new Set([a.id, ...o.filter((e) => e.mimeType === Q).map((e) => e.id)]);
100
+ for (let t of [...o].sort(i)) {
101
+ let i = $(e, t.id);
102
+ if (r.has(i)) continue;
103
+ r.add(i);
104
+ let o = t.parents?.find((e) => c.has(e)) ?? a.id, s = t.mimeType === Q, l = s ? "unsupported" : Te(t.mimeType);
105
+ n.push({
106
+ id: i,
107
+ title: t.name,
108
+ content: "",
109
+ parentId: $(e, o),
110
+ isFolder: s,
111
+ isExpanded: !1,
112
+ external: {
113
+ sourceId: e,
114
+ fileId: t.id,
115
+ mimeType: t.mimeType,
116
+ view: l,
117
+ extension: s ? void 0 : Ee(t),
118
+ contentUrl: s || l === "unsupported" ? void 0 : De(t.id),
119
+ webViewLink: t.webViewLink
120
+ }
121
+ });
122
+ }
123
+ }
124
+ return n;
125
+ }
126
+ function ke(e) {
127
+ return e?.folders?.length ? e.folders : (e?.folderIds ?? []).map((e) => ({
128
+ id: e,
129
+ name: e === "root" ? "My Drive" : e
130
+ }));
131
+ }
132
+ async function Ae(e) {
133
+ let t = ke(e.credentials);
134
+ if (t.length === 0) return [];
135
+ let n = await Promise.allSettled(t.map((e) => we(`/folder-tree?folderId=${encodeURIComponent(e.id)}`))), r = n.flatMap((e) => e.status === "fulfilled" ? [e.value] : []);
136
+ if (r.length === 0) throw n[0].reason;
137
+ return Oe(e.id, r);
138
+ }
139
+ function je(e) {
140
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
141
+ }
142
+ function Me(e) {
143
+ return e.split(/\r?\n\r?\n/).map((e) => e.trim()).filter(Boolean).map((e) => `<p>${je(e).replace(/\r?\n/g, "<br>")}</p>`).join("");
144
+ }
145
+ function Ne(e) {
146
+ if (typeof DOMParser > "u") {
147
+ let t = e.match(/<body[^>]*>([\s\S]*)<\/body>/i);
148
+ return (t ? t[1] : e).replace(/<(script|style)[\s\S]*?<\/\1>/gi, "");
149
+ }
150
+ let t = new DOMParser().parseFromString(e, "text/html"), n = /* @__PURE__ */ new Map();
151
+ for (let e of Array.from(t.querySelectorAll("style"))) for (let t of (e.textContent ?? "").matchAll(/\.([A-Za-z0-9_-]+)\s*\{([^}]*)\}/g)) n.set(t[1], `${n.get(t[1]) ?? ""};${t[2]}`);
152
+ t.querySelectorAll("script, style, meta, link, title").forEach((e) => e.remove());
153
+ for (let e of Array.from(t.body.querySelectorAll("span"))) {
154
+ let r = [...Array.from(e.classList).map((e) => n.get(e) ?? ""), e.getAttribute("style") ?? ""].join(";"), i = Array.from(e.childNodes), a = (e) => {
155
+ let n = t.createElement(e);
156
+ i.forEach((e) => n.appendChild(e)), i = [n];
157
+ };
158
+ /font-weight:\s*(700|800|900|bold)/i.test(r) && a("strong"), /font-style:\s*italic/i.test(r) && a("em"), /text-decoration[^;]*underline/i.test(r) && a("u"), /text-decoration[^;]*line-through/i.test(r) && a("s"), e.replaceWith(...i);
159
+ }
160
+ for (let e of Array.from(t.body.querySelectorAll("a[href]"))) try {
161
+ let t = new URL(e.getAttribute("href"));
162
+ t.hostname === "www.google.com" && t.pathname === "/url" && t.searchParams.get("q") && e.setAttribute("href", t.searchParams.get("q"));
163
+ } catch {}
164
+ for (let e of Array.from(t.body.querySelectorAll("[class], [style], [id]"))) e.removeAttribute("class"), e.removeAttribute("style"), e.removeAttribute("id");
165
+ return t.body.innerHTML.trim();
166
+ }
167
+ async function Pe(e) {
168
+ let t = e.external;
169
+ if (!t?.contentUrl) return "";
170
+ let n = await fetch(t.contentUrl, { credentials: "same-origin" });
171
+ if (!n.ok) {
172
+ let e = {};
173
+ try {
174
+ e = await n.json();
175
+ } catch {}
176
+ throw new Ce(e?.error || `Couldn't load the file (${n.status})`, n.status, e);
177
+ }
178
+ let r = await n.text();
179
+ return t.mimeType === X ? Ne(r) : Me(r);
180
+ }
181
+ var Fe = {
182
+ canList: (e) => e.type === "gdocs",
183
+ listDocuments: (e) => Ae(e),
184
+ loadDocumentContent: Pe
46
185
  };
47
- //#endregion
48
- //#region src/workspace/ResearchWorkspaceView.tsx
49
- function $() {
50
- let { activeView: t, toggleToDocs: n, toggleToResearch: r, filesSidebarRequestId: i } = b(), { chatTabs: a, activeChatId: s, openChat: l, newChat: u, closeChat: f, closeChats: p } = R(), { sendMessage: m } = c(), { signIn: h } = o(), g = ae(), [_, v] = U(null), [y, x] = U(null), [S, C] = U(!1), [w, T] = U(null);
51
- H(() => {
52
- oe.setLang("en"), q.setColor("default");
186
+ // src/workspace/ResearchWorkspaceView.tsx
187
+ function Ie({ SidebarComponent: e = ye, SidebarContentComponent: t = be, openSidebarEvent: n } = {}) {
188
+ let { activeView: r, toggleToDocs: i, toggleToResearch: a, filesSidebarRequestId: o, requestFilesSidebar: s } = b(), { chatTabs: c, activeChatId: u, openChat: d, newChat: f, closeChat: p, closeChats: m } = P(), { sendMessage: h } = z(), { signIn: g } = R(), v = ge(), [y, x] = H(null), [S, C] = H(null), [w, T] = H(!1), [E, D] = H(null);
189
+ V(() => {
190
+ if (n) return window.addEventListener(n, s), () => window.removeEventListener(n, s);
191
+ }, [n, s]), V(() => {
192
+ ve.setLang("en"), _e.setColor("default");
53
193
  }, []);
54
- let E = ie(!1);
55
- H(() => {
56
- if (E.current) return;
57
- E.current = !0;
58
- let e = g.get("docs"), t = g.get("chat");
59
- e ? (x(e), n()) : t && (l(t), r()), C(!0);
60
- }, []), H(() => {
61
- if (typeof window > "u" || !S) return;
194
+ let O = he(!1);
195
+ V(() => {
196
+ if (O.current) return;
197
+ O.current = !0;
198
+ let e = v.get("docs"), t = v.get("chat");
199
+ e ? (C(e), i()) : t && (d(t), a()), T(!0);
200
+ }, []), V(() => {
201
+ if (typeof window > "u" || !w) return;
62
202
  let e = new URL(window.location.href);
63
- e.searchParams.set("chat", t === "research" ? s ?? "" : ""), e.searchParams.set("docs", t === "docs" ? _ ?? "" : "");
64
- let n = `${e.pathname}?${e.searchParams.toString()}${e.hash}`;
65
- n !== `${window.location.pathname}${window.location.search}${window.location.hash}` && window.history.replaceState(null, "", n);
203
+ e.searchParams.set("chat", r === "research" ? u ?? "" : ""), e.searchParams.set("docs", r === "docs" ? y ?? "" : "");
204
+ let t = `${e.pathname}?${e.searchParams.toString()}${e.hash}`;
205
+ t !== `${window.location.pathname}${window.location.search}${window.location.hash}` && window.history.replaceState(null, "", t);
66
206
  }, [
67
- t,
68
- s,
69
- _,
70
- S
207
+ r,
208
+ u,
209
+ y,
210
+ w
71
211
  ]);
72
- let D = a.map((e) => ({
212
+ let k = c.map((e) => ({
73
213
  id: e.id,
74
214
  title: e.title,
75
215
  kind: "chat"
76
- })), O = (e) => {
77
- l(e), r();
78
- }, k = (e) => {
79
- let { closedWasActive: t, nextActiveId: r } = f(e);
80
- t && !r && n();
81
- }, A = (e) => {
82
- let { closedWasActive: t, nextActiveId: r } = p(e);
83
- t && !r && n();
84
- }, j = () => {
85
- u(), r();
86
- }, M = V((e) => (l(e), r(), !0), [l, r]);
87
- H(() => (e({ onOpenChat: M }), () => e({ onOpenChat: void 0 })), [M]);
88
- let N = async (e, t) => Z(e, X(t)), P = async (e, t) => Q(e, X(t)), F = V((e) => {
89
- u(), r(), T(e);
90
- }, [u, r]);
91
- H(() => {
92
- w && s && (m(w), T(null));
216
+ })), ee = (e) => {
217
+ d(e), a();
218
+ }, te = (e) => {
219
+ let { closedWasActive: t, nextActiveId: n } = p(e);
220
+ t && !n && i();
221
+ }, ne = (e) => {
222
+ let { closedWasActive: t, nextActiveId: n } = m(e);
223
+ t && !n && i();
224
+ }, A = () => {
225
+ f(), a();
226
+ }, j = B((e) => (d(e), a(), !0), [d, a]);
227
+ V(() => (L({ onOpenChat: j }), () => L({ onOpenChat: void 0 })), [j]);
228
+ let M = async (e, t) => q(e, K(t)), N = async (e, t) => J(e, K(t)), F = B((e) => {
229
+ f(), a(), D(e);
230
+ }, [f, a]);
231
+ V(() => {
232
+ E && u && (h(E), D(null));
93
233
  }, [
94
- w,
95
- s,
96
- m
234
+ E,
235
+ u,
236
+ h
97
237
  ]);
98
238
  let I = {
99
- extraTabs: D,
100
- activeExtraTabId: t === "research" ? s ?? void 0 : void 0,
101
- onExtraTabSelect: O,
102
- onExtraTabClose: k,
103
- onExtraTabsClose: A,
104
- onExtraTabAdd: j,
105
- onFileTabSelect: n,
106
- initialDocId: y,
107
- onActiveDocumentChange: v,
108
- onGenerateTips: N,
109
- onGenerateTopics: P,
239
+ extraTabs: k,
240
+ activeExtraTabId: r === "research" ? u ?? void 0 : void 0,
241
+ onExtraTabSelect: ee,
242
+ onExtraTabClose: te,
243
+ onExtraTabsClose: ne,
244
+ onExtraTabAdd: A,
245
+ onFileTabSelect: i,
246
+ initialDocId: S,
247
+ onActiveDocumentChange: x,
248
+ onGenerateTips: M,
249
+ onGenerateTopics: N,
110
250
  onSearchTopic: F,
111
- onSignIn: h
251
+ onSignIn: g,
252
+ fileSourceProvider: Fe
112
253
  };
113
- return t === "docs" ? /* @__PURE__ */ W(K, {
114
- SidebarComponent: J,
115
- SidebarContentComponent: Y,
116
- belowMainContent: /* @__PURE__ */ W(d, {}),
117
- openFilesSidebarSignal: i,
254
+ return r === "docs" ? /* @__PURE__ */ U(G, {
255
+ SidebarComponent: e,
256
+ SidebarContentComponent: t,
257
+ belowMainContent: /* @__PURE__ */ U(_, {}),
258
+ openFilesSidebarSignal: o,
118
259
  ...I
119
- }) : /* @__PURE__ */ W(K, {
120
- SidebarComponent: J,
121
- SidebarContentComponent: Y,
122
- mainContent: /* @__PURE__ */ W(B, {}),
123
- openFilesSidebarSignal: i,
260
+ }) : /* @__PURE__ */ U(G, {
261
+ SidebarComponent: e,
262
+ SidebarContentComponent: t,
263
+ mainContent: /* @__PURE__ */ U(l, {}),
264
+ openFilesSidebarSignal: o,
124
265
  ...I
125
266
  });
126
267
  }
127
- //#endregion
128
- //#region src/workspace/QwkSearchWorkspaceApp.tsx
129
- function se(e) {
130
- return /* @__PURE__ */ W(L, {
131
- ...e,
268
+ // src/workspace/QwkSearchWorkspaceApp.tsx
269
+ function Le({ SidebarComponent: e, SidebarContentComponent: t, openSidebarEvent: n, ...r }) {
270
+ return /* @__PURE__ */ U(M, {
271
+ ...r,
132
272
  docsEnabled: !0,
133
- children: /* @__PURE__ */ W($, {})
273
+ children: /* @__PURE__ */ U(Ie, {
274
+ SidebarComponent: e,
275
+ SidebarContentComponent: t,
276
+ openSidebarEvent: n
277
+ })
134
278
  });
135
279
  }
136
- //#endregion
137
- export { p as ARTICLE_TOOLBAR_SHORTCUTS, _ as ArticleAIResponse, A as ArticleActionButtons, r as ArticleContent, a as ArticleFollowupQuestions, k as ArticlePanelHeader, i as ArticlePromptInput, P as CategoryDock, d as ChatInputBox, f as ChatProvider, B as ChatWindow, x as CookieConsent, v as ExtractPanelProvider, S as HistoryDialogs, re as HistoryDropdown, C as KokoroVoiceSelector, te as LexicalArticleViewer, E as MainViewProvider, N as QwkSearchApp, L as QwkSearchProviders, se as QwkSearchWorkspaceApp, $ as ResearchWorkspaceView, D as SPOTLIGHT_LINKS, T as SPOTLIGHT_PREFIXES, m as SessionProvider, z as SpotlightPalette, g as UnifiedMarkdown, ee as VoiceSettingsPanel, s as cn, e as configureResearchAgentUI, l as formatMessageTime, I as formatTimeDifference, n as formatToolbarShortcut, Z as getPageTips, Q as getTopicSearches, X as htmlToPlainText, F as matchSpotlight, w as openSpotlight, M as parsePrefix, t as researchAgentUIConfig, c as useChat, u as useChatState, R as useChatTabs, O as useChunkErrorReload, y as useExtractPanel, ne as useHistoryState, j as useKokoroTTS, b as useMainView, o as useSession, h as useTextToSpeech };
280
+ export { i as ARTICLE_TOOLBAR_SHORTCUTS, re as ArticleAIResponse, f as ArticleActionButtons, h as ArticleContent, g as ArticleFollowupQuestions, t as ArticlePanelHeader, a as ArticlePromptInput, m as ArticleReaderMount, x as CategoryDock, _ as ChatInputBox, e as ChatProvider, l as ChatWindow, N as CookieConsent, le as ExtractPanelProvider, n as HistoryDialogs, oe as HistoryDropdown, F as KokoroVoiceSelector, r as LexicalArticleViewer, ae as MainViewProvider, O as QWKSEARCH_DEFAULT_ORIGIN, ne as QwkSearchApp, M as QwkSearchProviders, Le as QwkSearchWorkspaceApp, Ie as ResearchWorkspaceView, I as SPOTLIGHT_LINKS, E as SPOTLIGHT_PREFIXES, de as SessionProvider, j as SpotlightPalette, ie as UnifiedMarkdown, ee as VoiceSettingsPanel, u as buildConnectStartUrl, pe as cn, L as configureResearchAgentUI, d as createQwkSearchConnectAuthClient, me as formatMessageTime, fe as formatTimeDifference, k as formatToolbarShortcut, q as getPageTips, p as getQwkSearchApiKey, J as getTopicSearches, K as htmlToPlainText, D as isChunkLoadError, o as isQwkSearchApiRequest, T as matchSpotlight, te as openSpotlight, v as parsePrefix, y as reloadForChunkError, se as researchAgentUIConfig, c as setQwkSearchApiKey, z as useChat, C as useChatState, P as useChatTabs, A as useChunkErrorReload, ce as useExtractPanel, s as useHistoryState, w as useKokoroTTS, b as useMainView, ue as useOptionalExtractPanel, R as useSession, S as useTextToSpeech };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "research-agent-ui",
3
3
  "description": "The QwkSearch app UI: conversation window, article reader, search config, file uploads, chat history, and the app shell — exported both with and without the REASON editor and its sidebar",
4
- "version": "0.1.293",
4
+ "version": "0.1.294",
5
5
  "author": "vtempest",
6
6
  "repository": {
7
7
  "type": "git",
@@ -37,6 +37,14 @@
37
37
  "types": "./src/lib/file-sources.ts",
38
38
  "import": "./src/lib/file-sources.ts"
39
39
  },
40
+ "./google-drive": {
41
+ "types": "./src/lib/google-drive-source.ts",
42
+ "import": "./src/lib/google-drive-source.ts"
43
+ },
44
+ "./chunk-error-reload": {
45
+ "types": "./src/app/useChunkErrorReload.ts",
46
+ "import": "./src/app/useChunkErrorReload.ts"
47
+ },
40
48
  "./settings": {
41
49
  "types": "./src/settings/index.ts",
42
50
  "import": "./src/settings/index.ts"
@@ -48,6 +56,7 @@
48
56
  "files": [
49
57
  "dist",
50
58
  "src/settings",
59
+ "src/app/useChunkErrorReload.ts",
51
60
  "README.md"
52
61
  ],
53
62
  "sideEffects": [
@@ -82,72 +91,66 @@
82
91
  "react-dom": ">=18"
83
92
  },
84
93
  "dependencies": {
85
- "@huggingface/transformers": "^4.2.0",
86
- "@radix-ui/react-dialog": "^1.1.23",
87
- "@radix-ui/react-dropdown-menu": "^2.1.24",
88
- "@radix-ui/react-popover": "^1.1.23",
89
- "@radix-ui/react-slot": "^1.3.3",
90
- "@radix-ui/react-tooltip": "^1.2.16",
91
- "@ricky0123/vad-web": "^0.0.30",
92
- "ai": "^7.0.94",
93
- "chat-agent-toolkit": "^1.2.426",
94
+ "@radix-ui/react-dialog": "^1.3.1",
95
+ "@radix-ui/react-dropdown-menu": "^2.2.1",
96
+ "@radix-ui/react-popover": "^1.3.1",
97
+ "@radix-ui/react-slot": "^1.5.0",
98
+ "@radix-ui/react-tooltip": "^1.4.1",
99
+ "ai": "^7.0.159",
100
+ "chat-agent-toolkit": "^1.2.427",
94
101
  "class-variance-authority": "^0.7.1",
95
102
  "clsx": "^2.1.1",
96
- "domain-rank": "^0.1.415",
97
- "dompurify": "^3.4.15",
98
- "drizzle-orm": "^0.45.2",
99
- "extract-webpage": "^1.2.420",
100
- "framer-motion": "^13.2.0",
101
- "fuse.js": "^7.5.0",
102
- "grab-url": "^1.6.22",
103
+ "dompurify": "^3.4.17",
104
+ "drizzle-orm": "^0.45.4",
105
+ "extract-webpage": "^1.2.421",
106
+ "grab-url": "^3.0.9",
103
107
  "html2canvas": "^1.4.1",
104
108
  "kokoro-js": "^1.2.1",
105
- "legal-terms-privacy-policy": "^0.1.103",
106
- "lucide-react": "^1.43.0",
107
- "markdown-to-jsx": "^9.10.2",
108
- "motion": "^13.2.0",
109
- "quantum-sphere-loading-icon": "^1.0.3",
110
- "qwksearch-api-client": "^1.0.98",
109
+ "legal-terms-privacy-policy": "^0.1.242",
110
+ "lucide-react": "^1.66.0",
111
+ "markdown-to-jsx": "^9.10.4",
112
+ "motion": "^14.9.0",
113
+ "quantum-sphere-loading-icon": "^1.0.6",
114
+ "qwksearch-api-client": "^1.0.237",
111
115
  "react-icons": "^5.7.0",
112
116
  "react-native-app-buttons": "^0.1.4",
113
117
  "react-textarea-autosize": "^8.5.9",
114
- "search-web-api": "^1.0.278",
115
- "shadcn-app-dock": "^0.1.276",
116
- "shadcn-theme-menu": "^1.1.47",
118
+ "search-autocomplete": "^0.1.0",
119
+ "search-web-api": "^1.0.279",
120
+ "shadcn-app-dock": "^0.1.277",
121
+ "shadcn-theme-menu": "^1.1.74",
117
122
  "sonner": "^2.0.8",
118
- "streamdown": "^2.6.0",
119
- "tailwind-merge": "^3.6.0",
120
- "tldts": "^7.4.12",
121
- "trending-news-api": "^0.1.154",
123
+ "streamdown": "^2.7.1",
124
+ "tailwind-merge": "^3.7.0",
125
+ "trending-news-api": "^0.1.155",
126
+ "education-playlists": "^0.1.102",
122
127
  "turndown": "^7.2.4",
123
- "use-voice-control": "^0.1.95",
124
- "use-weather-forecast": "^0.1.240"
128
+ "use-voice-control": "^0.1.235",
129
+ "use-weather-forecast": "^0.1.379"
125
130
  },
126
131
  "devDependencies": {
127
- "@cloudflare/workers-types": "^5.20260908.1",
128
- "@storybook/react-vite": "^10.6.0",
132
+ "@cloudflare/workers-types": "^5.20261101.1",
133
+ "@storybook/react-vite": "^10.6.5",
129
134
  "@tailwindcss/vite": "^4.3.3",
130
135
  "@testing-library/react": "^16.3.3",
131
- "@types/react": "^19.2.18",
132
- "@types/react-dom": "^19.2.7",
136
+ "@types/react": "^19.3.0",
137
+ "@types/react-dom": "^19.3.0",
133
138
  "@types/turndown": "^5.0.6",
134
- "@vitejs/plugin-react": "^6.1.1",
135
- "@vitest/coverage-v8": "^5.0.0",
136
- "easydrawer": "^0.0.18",
139
+ "@vitejs/plugin-react": "^6.1.2",
140
+ "@vitest/coverage-v8": "^5.0.5",
137
141
  "itty-router": "^5.0.24",
138
- "jsdom": "^30.0.1",
139
- "katex": "^0.18.7",
140
- "next": "^16.3.4",
141
- "react": "^19.2.8",
142
- "react-dom": "^19.2.8",
143
- "react-reason-editor": "^1.0.228",
144
- "react-reason-editor-sidebar": "^0.1.145",
145
- "storybook": "^10.6.0",
142
+ "jsdom": "^30.1.3",
143
+ "next": "^16.4.3",
144
+ "react": "^19.3.1",
145
+ "react-dom": "^19.3.1",
146
+ "react-reason-editor": "^1.0.229",
147
+ "react-reason-editor-sidebar": "^0.1.284",
148
+ "storybook": "^10.6.5",
146
149
  "tailwindcss": "^4.3.3",
147
150
  "typescript": "^7.0.2",
148
- "vite": "^8.2.2",
149
- "vitest": "^5.0.0",
150
- "wrangler": "^4.130.0"
151
+ "vite": "^8.3.5",
152
+ "vitest": "^5.0.5",
153
+ "wrangler": "^4.156.1"
151
154
  },
152
155
  "license": "rights.institute/PROSPER"
153
- }
156
+ }
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+
5
+ const RELOAD_KEY = 'qwk:chunk-error-reload';
6
+
7
+ /**
8
+ * How long after one chunk-error reload another is refused. Long enough that a
9
+ * build which is genuinely missing a chunk cannot put the tab in a reload loop,
10
+ * short enough that a tab which recovered and then outlived a *later* deploy in
11
+ * the same session still gets its one reload.
12
+ */
13
+ const RELOAD_COOLDOWN_MS = 60_000;
14
+
15
+ /**
16
+ * The messages browsers and bundlers give a chunk that could not be fetched:
17
+ * webpack's `ChunkLoadError`, and the native dynamic-`import()` failures that
18
+ * Vite (and so vinext) builds surface — Chromium's "Failed to fetch dynamically
19
+ * imported module", Firefox's "error loading dynamically imported module" and
20
+ * Safari's "Importing a module script failed".
21
+ */
22
+ const CHUNK_ERROR_PATTERN =
23
+ /Loading (CSS )?chunk|ChunkLoadError|Failed to fetch dynamically imported module|error loading dynamically imported module|Importing a module script failed|Unable to preload CSS/i;
24
+
25
+ /** Whether `error` (an Error, a message, or a rejection reason) is a failed chunk fetch. */
26
+ export function isChunkLoadError(error: unknown): boolean {
27
+ if (typeof error === 'string') return CHUNK_ERROR_PATTERN.test(error);
28
+ if (error && typeof error === 'object') {
29
+ const { name, message } = error as { name?: unknown; message?: unknown };
30
+ return (
31
+ name === 'ChunkLoadError' ||
32
+ (typeof message === 'string' && CHUNK_ERROR_PATTERN.test(message))
33
+ );
34
+ }
35
+ return false;
36
+ }
37
+
38
+ /**
39
+ * Reloads the page if `error` is a failed chunk fetch and no such reload
40
+ * happened in the last {@link RELOAD_COOLDOWN_MS}. Returns whether it reloaded,
41
+ * so a caller can keep showing a loading state instead of an error.
42
+ */
43
+ export function reloadForChunkError(error: unknown): boolean {
44
+ if (typeof window === 'undefined' || !isChunkLoadError(error)) return false;
45
+ try {
46
+ const last = Number(window.sessionStorage.getItem(RELOAD_KEY));
47
+ if (last && Date.now() - last < RELOAD_COOLDOWN_MS) return false;
48
+ window.sessionStorage.setItem(RELOAD_KEY, String(Date.now()));
49
+ } catch {
50
+ // Without storage there is no loop guard, so don't reload at all.
51
+ return false;
52
+ }
53
+ window.location.reload();
54
+ return true;
55
+ }
56
+
57
+ /**
58
+ * Deployments replace `_next/static` chunks with newly-hashed filenames, so a
59
+ * browser tab left open from before a deploy will 404 when it tries to fetch
60
+ * an old chunk (e.g. on a client-side navigation). Reload once to pick up the
61
+ * new build instead of leaving the user stuck on a broken page.
62
+ *
63
+ * This only sees failures nothing else handled. A lazy component's failed
64
+ * chunk is caught by the nearest error boundary instead, so boundaries around
65
+ * lazy subtrees should call {@link reloadForChunkError} themselves.
66
+ */
67
+ export function useChunkErrorReload(): void {
68
+ useEffect(() => {
69
+ const onError = (event: ErrorEvent): void => {
70
+ reloadForChunkError(event.error ?? event.message);
71
+ };
72
+ const onRejection = (event: PromiseRejectionEvent): void => {
73
+ reloadForChunkError(event.reason);
74
+ };
75
+ // Vite's preload helper dispatches this when a dynamic import's
76
+ // dependencies fail to load; `payload` is the underlying error.
77
+ const onPreloadError = (event: Event): void => {
78
+ if (reloadForChunkError((event as Event & { payload?: unknown }).payload ?? 'Loading chunk')) {
79
+ event.preventDefault();
80
+ }
81
+ };
82
+
83
+ window.addEventListener('error', onError);
84
+ window.addEventListener('unhandledrejection', onRejection);
85
+ window.addEventListener('vite:preloadError', onPreloadError);
86
+ return () => {
87
+ window.removeEventListener('error', onError);
88
+ window.removeEventListener('unhandledrejection', onRejection);
89
+ window.removeEventListener('vite:preloadError', onPreloadError);
90
+ };
91
+ }, []);
92
+ }
@@ -118,3 +118,26 @@ export function getSettingsSchema(): {
118
118
  fieldsBySection: settingsFieldsBySection,
119
119
  };
120
120
  }
121
+
122
+ /**
123
+ * One settings section as a standalone page a host can mount in its own
124
+ * settings area — the "external page" form of {@link SettingsSectionSchema}.
125
+ */
126
+ export interface SettingsPageSchema extends SettingsSectionSchema {
127
+ /** Route the host serves this section at, e.g. `/settings/research/models`. */
128
+ href: string;
129
+ }
130
+
131
+ /**
132
+ * The settings sections as pages under `basePath`, in menu order. A host that
133
+ * embeds the research agent registers these alongside its own settings pages
134
+ * (`basePath` is where it mounts the research settings), so every section the
135
+ * agent exposes is reachable from the host's global settings.
136
+ */
137
+ export function getSettingsPages(basePath = "/settings"): SettingsPageSchema[] {
138
+ const base = basePath.replace(/\/+$/, "");
139
+ return settingsSections.map((section) => ({
140
+ ...section,
141
+ href: `${base}/${section.key}`,
142
+ }));
143
+ }