@veluai/velu 0.2.4 → 0.2.5
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/package.json
CHANGED
|
@@ -160,8 +160,32 @@ function tokenizeAnswer(answer) {
|
|
|
160
160
|
return tokens;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/* A citation's in-site target. Real backend sources carry a `url` (and `path`
|
|
164
|
+
is a route like "/essentials/code"); the demo's `path` is a fake file path,
|
|
165
|
+
so those stay inert. */
|
|
166
|
+
function sourceHref(s) {
|
|
167
|
+
if (s?.url) return s.url;
|
|
168
|
+
if (s?.path && s.path.startsWith('/')) return s.path;
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Source click: SPA-navigate same-origin targets via onNavigate (keeps the chat
|
|
173
|
+
panel open + scrolls to the heading); otherwise let the <a href> navigate. */
|
|
174
|
+
function onSourceClick(e, s, onNavigate) {
|
|
175
|
+
const href = sourceHref(s);
|
|
176
|
+
if (!href) { e.preventDefault(); return; }
|
|
177
|
+
if (!onNavigate) return; // no SPA hook → let the browser follow the href
|
|
178
|
+
try {
|
|
179
|
+
const u = new URL(href, window.location.origin);
|
|
180
|
+
if (u.origin === window.location.origin) {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
onNavigate(u.pathname + u.search + u.hash);
|
|
183
|
+
}
|
|
184
|
+
} catch { /* malformed — fall back to href */ }
|
|
185
|
+
}
|
|
186
|
+
|
|
163
187
|
/* Render partial tokens as paragraphs (split on \n\n, single \n → <br>). */
|
|
164
|
-
function renderStream(tokens, sources) {
|
|
188
|
+
function renderStream(tokens, sources, onNavigate) {
|
|
165
189
|
const paragraphs = [[]];
|
|
166
190
|
for (const tk of tokens) {
|
|
167
191
|
if (tk.kind === 't' && tk.v.includes('\n\n')) {
|
|
@@ -179,13 +203,14 @@ function renderStream(tokens, sources) {
|
|
|
179
203
|
<p key={pi}>
|
|
180
204
|
{para.map((tk, i) => {
|
|
181
205
|
if (tk.kind === 'cite') {
|
|
206
|
+
const src = sources?.find((s) => s.num === tk.n);
|
|
182
207
|
return (
|
|
183
208
|
<a
|
|
184
209
|
key={i}
|
|
185
210
|
className="velu-chatbot__cite"
|
|
186
|
-
href=
|
|
187
|
-
onClick={(e) => e
|
|
188
|
-
title={
|
|
211
|
+
href={sourceHref(src) || '#'}
|
|
212
|
+
onClick={(e) => onSourceClick(e, src, onNavigate)}
|
|
213
|
+
title={src?.title}
|
|
189
214
|
>
|
|
190
215
|
{tk.n}
|
|
191
216
|
</a>
|
|
@@ -331,6 +356,7 @@ function AiMsg({
|
|
|
331
356
|
onFollowup,
|
|
332
357
|
messageId,
|
|
333
358
|
onFeedback,
|
|
359
|
+
onNavigate,
|
|
334
360
|
}) {
|
|
335
361
|
const thinking = tokens.length === 0;
|
|
336
362
|
const [vote, setVote] = useState(null); // 'up' | 'down' | null
|
|
@@ -367,7 +393,7 @@ function AiMsg({
|
|
|
367
393
|
|
|
368
394
|
{!thinking && (
|
|
369
395
|
<div className="velu-chatbot__answer">
|
|
370
|
-
{renderStream(tokens, sources)}
|
|
396
|
+
{renderStream(tokens, sources, onNavigate)}
|
|
371
397
|
{streaming && <span className="velu-chatbot__caret" />}
|
|
372
398
|
|
|
373
399
|
{!streaming && sources?.length > 0 && (
|
|
@@ -377,8 +403,8 @@ function AiMsg({
|
|
|
377
403
|
<a
|
|
378
404
|
key={s.num}
|
|
379
405
|
className="velu-chatbot__source"
|
|
380
|
-
href=
|
|
381
|
-
onClick={(e) => e
|
|
406
|
+
href={sourceHref(s) || '#'}
|
|
407
|
+
onClick={(e) => onSourceClick(e, s, onNavigate)}
|
|
382
408
|
>
|
|
383
409
|
<span className="velu-chatbot__source-num">{s.num}</span>
|
|
384
410
|
<span className="velu-chatbot__source-meta">
|
|
@@ -489,6 +515,7 @@ export default function Chatbot({
|
|
|
489
515
|
onClose,
|
|
490
516
|
ask,
|
|
491
517
|
onFeedback,
|
|
518
|
+
onNavigate,
|
|
492
519
|
className = '',
|
|
493
520
|
...rest
|
|
494
521
|
}) {
|
|
@@ -741,6 +768,7 @@ export default function Chatbot({
|
|
|
741
768
|
streaming={m.streaming}
|
|
742
769
|
messageId={m.messageId}
|
|
743
770
|
onFeedback={onFeedback}
|
|
771
|
+
onNavigate={onNavigate}
|
|
744
772
|
onFollowup={(f) => send(f)}
|
|
745
773
|
/>
|
|
746
774
|
),
|
package/src/runtime/App.jsx
CHANGED
|
@@ -1558,7 +1558,7 @@ function DocsPage() {
|
|
|
1558
1558
|
approaches (see IntersectionObserver above). */}
|
|
1559
1559
|
{/* Ask-a-question bar — opens the AI chatbot, which runs on the
|
|
1560
1560
|
deployed site. Hidden in the local dev preview. */}
|
|
1561
|
-
{!IS_DEV_PREVIEW && (
|
|
1561
|
+
{!IS_DEV_PREVIEW && !chatOpen && (
|
|
1562
1562
|
<AskBar
|
|
1563
1563
|
onSubmit={askAI}
|
|
1564
1564
|
style={{
|
|
@@ -1623,6 +1623,7 @@ function DocsPage() {
|
|
|
1623
1623
|
onClose={() => setChatOpen(false)}
|
|
1624
1624
|
ask={assistant?.ask}
|
|
1625
1625
|
onFeedback={assistant?.sendFeedback}
|
|
1626
|
+
onNavigate={navigate}
|
|
1626
1627
|
/>
|
|
1627
1628
|
</div>
|
|
1628
1629
|
);
|