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.
- package/README.md +42 -1
- package/dist/ArticleExtractPanel-25tRTnpl.cjs +2 -0
- package/dist/ArticleExtractPanel-D_y16YHG.js +389 -0
- package/dist/ChatConversationThread-BIJ8aNug.js +1428 -0
- package/dist/ChatConversationThread-BoNH0wvs.cjs +17 -0
- package/dist/DownloadsDialog-CCPj0hA8.cjs +1 -0
- package/dist/{DownloadsDialog-bTEDe2DI.js → DownloadsDialog-DvS23Dh-.js} +8 -9
- package/dist/api/handlers/autocomplete.d.ts +6 -9
- package/dist/api/handlers/openai-transcription.d.ts +53 -0
- package/dist/api/handlers/transcript.d.ts +33 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api.cjs +10 -10
- package/dist/api.mjs +161 -208
- package/dist/app/index.d.ts +1 -1
- package/dist/app/useChunkErrorReload.d.ts +13 -2
- package/dist/components/ArticleReader/ArticleReaderMount.d.ts +5 -0
- package/dist/components/ArticleReader/ExtractPanelContext.d.ts +17 -1
- package/dist/components/ChatConversation/MessageBubble/AssistantMessageActions.d.ts +2 -1
- package/dist/components/ChatConversation/background-art.d.ts +3 -1
- package/dist/components/ChatConversation/background-cache.d.ts +41 -0
- package/dist/components/Footer.d.ts +5 -4
- package/dist/components/Footer.stories.d.ts +3 -3
- package/dist/config.cjs +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.mjs +4 -3
- package/dist/dialog-7GDz0sWx.cjs +1 -0
- package/dist/dialog-Cufn2u50.js +80 -0
- package/dist/hooks/voice/useKokoroTTS.d.ts +2 -0
- package/dist/hooks/voice/useTextToVoice.d.ts +2 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.mjs +4 -2
- package/dist/lib/file-sources.d.ts +8 -0
- package/dist/lib/google-drive-source.d.ts +100 -0
- package/dist/lib/kokoro.d.ts +14 -2
- package/dist/lib/qwksearch-connect.d.ts +79 -0
- package/dist/settings/index.d.ts +15 -0
- package/dist/src-GPr1ceY8.js +6165 -0
- package/dist/src-ebmQu_if.cjs +34 -0
- package/dist/tooltip-Bl3IdX-I.js +179 -0
- package/dist/tooltip-DmUO6ZUz.cjs +1 -0
- package/dist/types/fileSource.d.ts +9 -0
- package/dist/workspace/QwkSearchWorkspaceApp.d.ts +9 -2
- package/dist/workspace/ResearchWorkspaceView.d.ts +18 -4
- package/dist/workspace.cjs +1 -1
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.mjs +234 -91
- package/package.json +54 -51
- package/src/app/useChunkErrorReload.ts +92 -0
- package/src/settings/index.ts +23 -0
- package/src/settings/search.json +19 -1
- package/dist/DownloadsDialog-Ca6aRRcP.cjs +0 -1
- package/dist/src-HL9JjPc-.js +0 -8218
- package/dist/src-r4BsCIB4.cjs +0 -51
package/dist/workspace.mjs
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use client";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import "
|
|
14
|
-
import "
|
|
15
|
-
|
|
16
|
-
|
|
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(/ /g, " ").replace(/\s+/g, " ").trim();
|
|
19
18
|
}
|
|
20
|
-
var
|
|
19
|
+
var q = async (e, t) => {
|
|
21
20
|
try {
|
|
22
|
-
let n = await
|
|
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
|
-
},
|
|
32
|
+
}, J = async (e, t) => {
|
|
34
33
|
try {
|
|
35
|
-
let n = await
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
let e =
|
|
59
|
-
e ? (
|
|
60
|
-
}, []),
|
|
61
|
-
if (typeof window > "u" || !
|
|
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",
|
|
64
|
-
let
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
207
|
+
r,
|
|
208
|
+
u,
|
|
209
|
+
y,
|
|
210
|
+
w
|
|
71
211
|
]);
|
|
72
|
-
let
|
|
212
|
+
let k = c.map((e) => ({
|
|
73
213
|
id: e.id,
|
|
74
214
|
title: e.title,
|
|
75
215
|
kind: "chat"
|
|
76
|
-
})),
|
|
77
|
-
|
|
78
|
-
},
|
|
79
|
-
let { closedWasActive: t, nextActiveId:
|
|
80
|
-
t && !
|
|
81
|
-
},
|
|
82
|
-
let { closedWasActive: t, nextActiveId:
|
|
83
|
-
t && !
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
let
|
|
89
|
-
|
|
90
|
-
}, [
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
234
|
+
E,
|
|
235
|
+
u,
|
|
236
|
+
h
|
|
97
237
|
]);
|
|
98
238
|
let I = {
|
|
99
|
-
extraTabs:
|
|
100
|
-
activeExtraTabId:
|
|
101
|
-
onExtraTabSelect:
|
|
102
|
-
onExtraTabClose:
|
|
103
|
-
onExtraTabsClose:
|
|
104
|
-
onExtraTabAdd:
|
|
105
|
-
onFileTabSelect:
|
|
106
|
-
initialDocId:
|
|
107
|
-
onActiveDocumentChange:
|
|
108
|
-
onGenerateTips:
|
|
109
|
-
onGenerateTopics:
|
|
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:
|
|
251
|
+
onSignIn: g,
|
|
252
|
+
fileSourceProvider: Fe
|
|
112
253
|
};
|
|
113
|
-
return
|
|
114
|
-
SidebarComponent:
|
|
115
|
-
SidebarContentComponent:
|
|
116
|
-
belowMainContent: /* @__PURE__ */
|
|
117
|
-
openFilesSidebarSignal:
|
|
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__ */
|
|
120
|
-
SidebarComponent:
|
|
121
|
-
SidebarContentComponent:
|
|
122
|
-
mainContent: /* @__PURE__ */
|
|
123
|
-
openFilesSidebarSignal:
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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__ */
|
|
273
|
+
children: /* @__PURE__ */ U(Ie, {
|
|
274
|
+
SidebarComponent: e,
|
|
275
|
+
SidebarContentComponent: t,
|
|
276
|
+
openSidebarEvent: n
|
|
277
|
+
})
|
|
134
278
|
});
|
|
135
279
|
}
|
|
136
|
-
|
|
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.
|
|
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
|
-
"@
|
|
86
|
-
"@radix-ui/react-
|
|
87
|
-
"@radix-ui/react-
|
|
88
|
-
"@radix-ui/react-
|
|
89
|
-
"@radix-ui/react-
|
|
90
|
-
"
|
|
91
|
-
"
|
|
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
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
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.
|
|
106
|
-
"lucide-react": "^1.
|
|
107
|
-
"markdown-to-jsx": "^9.10.
|
|
108
|
-
"motion": "^
|
|
109
|
-
"quantum-sphere-loading-icon": "^1.0.
|
|
110
|
-
"qwksearch-api-client": "^1.0.
|
|
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-
|
|
115
|
-
"
|
|
116
|
-
"shadcn-
|
|
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.
|
|
119
|
-
"tailwind-merge": "^3.
|
|
120
|
-
"
|
|
121
|
-
"
|
|
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.
|
|
124
|
-
"use-weather-forecast": "^0.1.
|
|
128
|
+
"use-voice-control": "^0.1.235",
|
|
129
|
+
"use-weather-forecast": "^0.1.379"
|
|
125
130
|
},
|
|
126
131
|
"devDependencies": {
|
|
127
|
-
"@cloudflare/workers-types": "^5.
|
|
128
|
-
"@storybook/react-vite": "^10.6.
|
|
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.
|
|
132
|
-
"@types/react-dom": "^19.
|
|
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.
|
|
135
|
-
"@vitest/coverage-v8": "^5.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.
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"react": "^19.
|
|
142
|
-
"react-
|
|
143
|
-
"react-reason-editor": "^1.
|
|
144
|
-
"
|
|
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.
|
|
149
|
-
"vitest": "^5.0.
|
|
150
|
-
"wrangler": "^4.
|
|
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
|
+
}
|
package/src/settings/index.ts
CHANGED
|
@@ -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
|
+
}
|