@aravindc26/velu 0.13.15 → 0.13.17
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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { RootProvider } from 'fumadocs-ui/provider/next';
|
|
3
3
|
import './global.css';
|
|
4
|
-
import '
|
|
5
|
-
import '
|
|
6
|
-
import '
|
|
4
|
+
import '../../engine-core/css/shared.css';
|
|
5
|
+
import '../../engine-core/css/search.css';
|
|
6
|
+
import '../../engine-core/css/copy-page.css';
|
|
7
7
|
|
|
8
8
|
export const metadata = {
|
|
9
9
|
title: 'Preview',
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { Metadata } from 'next';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
import { getAppearance, getBannerConfig, getFontsConfig, getSeoConfig, getSiteDescription, getSiteFavicon, getSiteName, getSiteOrigin, getSitePrimaryColor } from '@/lib/velu';
|
|
3
|
+
import { getAppearance, getBannerConfig, getCliVersion, getFontsConfig, getSeoConfig, getSiteDescription, getSiteFavicon, getSiteName, getSiteOrigin, getSitePrimaryColor } from '@/lib/velu';
|
|
4
4
|
import { Providers } from '@/components/providers';
|
|
5
5
|
import { VeluAssistant } from '@/components/assistant';
|
|
6
6
|
import { VeluBanner } from '@/components/banner';
|
|
7
7
|
import './global.css';
|
|
8
|
-
import '
|
|
9
|
-
import '
|
|
10
|
-
import '
|
|
11
|
-
import '
|
|
8
|
+
import '../engine-core/css/shared.css';
|
|
9
|
+
import '../engine-core/css/search.css';
|
|
10
|
+
import '../engine-core/css/assistant.css';
|
|
11
|
+
import '../engine-core/css/copy-page.css';
|
|
12
12
|
|
|
13
13
|
function toAbsoluteUrl(origin: string, value: string): string {
|
|
14
14
|
const trimmed = value.trim();
|
|
@@ -80,6 +80,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
80
80
|
return (
|
|
81
81
|
<html lang="en" suppressHydrationWarning>
|
|
82
82
|
<head>
|
|
83
|
+
<meta name="generator" content={`velu-cli ${getCliVersion()}`} />
|
|
83
84
|
<link rel="sitemap" type="application/xml" href={`${siteOrigin}/sitemap.xml`} />
|
|
84
85
|
{fontsConfig && (() => {
|
|
85
86
|
// Collect Google Fonts families that don't use a custom source
|
|
@@ -68,6 +68,7 @@ function initAssistant() {
|
|
|
68
68
|
eventSource: EventSource | null;
|
|
69
69
|
expanded: boolean;
|
|
70
70
|
bootstrapped: boolean;
|
|
71
|
+
feedback: Record<string, 'up' | 'down'>;
|
|
71
72
|
} = {
|
|
72
73
|
conversationId: null,
|
|
73
74
|
conversationToken: null,
|
|
@@ -75,6 +76,7 @@ function initAssistant() {
|
|
|
75
76
|
eventSource: null,
|
|
76
77
|
expanded: false,
|
|
77
78
|
bootstrapped: false,
|
|
79
|
+
feedback: {},
|
|
78
80
|
};
|
|
79
81
|
|
|
80
82
|
const askBar = document.getElementById('veluAskBar')!;
|
|
@@ -93,6 +95,7 @@ function initAssistant() {
|
|
|
93
95
|
sessionStorage.setItem('velu-conv-id', state.conversationId || '');
|
|
94
96
|
sessionStorage.setItem('velu-conv-token', state.conversationToken || '');
|
|
95
97
|
sessionStorage.setItem('velu-last-seq', String(state.lastSeq));
|
|
98
|
+
sessionStorage.setItem('velu-feedback', JSON.stringify(state.feedback));
|
|
96
99
|
} catch {}
|
|
97
100
|
}
|
|
98
101
|
|
|
@@ -117,6 +120,7 @@ function initAssistant() {
|
|
|
117
120
|
state.conversationId = null;
|
|
118
121
|
state.conversationToken = null;
|
|
119
122
|
state.lastSeq = 0;
|
|
123
|
+
state.feedback = {};
|
|
120
124
|
if (state.eventSource) { state.eventSource.close(); state.eventSource = null; }
|
|
121
125
|
messagesEl.innerHTML = '';
|
|
122
126
|
chatInput.value = '';
|
|
@@ -143,6 +147,28 @@ function initAssistant() {
|
|
|
143
147
|
.catch(() => {});
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
function submitFeedback(messageId: number, rating: 'up' | 'down') {
|
|
151
|
+
return fetch(API_BASE + '/messages/' + messageId + '/feedback', {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers: { 'Content-Type': 'application/json' },
|
|
154
|
+
credentials: 'include',
|
|
155
|
+
body: JSON.stringify({ rating }),
|
|
156
|
+
}).then((r) => r.json());
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function retractFeedback(messageId: number) {
|
|
160
|
+
return fetch(API_BASE + '/messages/' + messageId + '/feedback', {
|
|
161
|
+
method: 'DELETE',
|
|
162
|
+
credentials: 'include',
|
|
163
|
+
}).then((r) => r.json());
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function saveFeedbackState() {
|
|
167
|
+
try {
|
|
168
|
+
sessionStorage.setItem('velu-feedback', JSON.stringify(state.feedback));
|
|
169
|
+
} catch {}
|
|
170
|
+
}
|
|
171
|
+
|
|
146
172
|
function formatContent(text: string, citations: any[]) {
|
|
147
173
|
let html = text
|
|
148
174
|
.replace(/&/g, '&')
|
|
@@ -162,9 +188,10 @@ function initAssistant() {
|
|
|
162
188
|
return html;
|
|
163
189
|
}
|
|
164
190
|
|
|
165
|
-
function addMessage(role: string, content: string, citations: any[] = []) {
|
|
191
|
+
function addMessage(role: string, content: string, citations: any[] = [], messageId?: number) {
|
|
166
192
|
const msgDiv = document.createElement('div');
|
|
167
193
|
msgDiv.className = 'velu-msg velu-msg-' + role;
|
|
194
|
+
if (messageId) msgDiv.setAttribute('data-message-id', String(messageId));
|
|
168
195
|
const bubble = document.createElement('div');
|
|
169
196
|
bubble.className = 'velu-msg-bubble velu-msg-bubble-' + role;
|
|
170
197
|
bubble.innerHTML = formatContent(content, citations);
|
|
@@ -188,17 +215,121 @@ function initAssistant() {
|
|
|
188
215
|
const actions = document.createElement('div');
|
|
189
216
|
actions.className = 'velu-msg-actions';
|
|
190
217
|
actions.innerHTML =
|
|
191
|
-
'<button class="velu-msg-action" title="Like"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg></button>' +
|
|
192
|
-
'<button class="velu-msg-action" title="Dislike"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"/></svg></button>' +
|
|
193
|
-
'<button class="velu-msg-action velu-msg-copy" title="Copy"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>'
|
|
218
|
+
'<button class="velu-msg-action velu-msg-like" title="Like"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg></button>' +
|
|
219
|
+
'<button class="velu-msg-action velu-msg-dislike" title="Dislike"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"/></svg></button>' +
|
|
220
|
+
'<button class="velu-msg-action velu-msg-copy" title="Copy"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>' +
|
|
221
|
+
'<button class="velu-msg-action velu-msg-regenerate" title="Regenerate"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"/><polyline points="23 20 23 14 17 14"/><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/></svg></button>';
|
|
194
222
|
msgDiv.appendChild(actions);
|
|
195
223
|
|
|
196
|
-
|
|
224
|
+
// Restore feedback state if we have it for this message
|
|
225
|
+
const existingRating = messageId ? state.feedback[messageId] : null;
|
|
226
|
+
const likeBtn = actions.querySelector('.velu-msg-like') as HTMLElement;
|
|
227
|
+
const dislikeBtn = actions.querySelector('.velu-msg-dislike') as HTMLElement;
|
|
228
|
+
if (existingRating === 'up') likeBtn.classList.add('velu-msg-action-active');
|
|
229
|
+
if (existingRating === 'down') dislikeBtn.classList.add('velu-msg-action-active');
|
|
230
|
+
|
|
231
|
+
// Like button
|
|
232
|
+
if (likeBtn && dislikeBtn) {
|
|
233
|
+
likeBtn.onclick = () => {
|
|
234
|
+
if (!messageId) return;
|
|
235
|
+
const isActive = likeBtn.classList.contains('velu-msg-action-active');
|
|
236
|
+
if (isActive) {
|
|
237
|
+
// Retract
|
|
238
|
+
likeBtn.classList.remove('velu-msg-action-active');
|
|
239
|
+
delete state.feedback[messageId];
|
|
240
|
+
saveFeedbackState();
|
|
241
|
+
retractFeedback(messageId).catch(() => {});
|
|
242
|
+
} else {
|
|
243
|
+
// Submit up
|
|
244
|
+
likeBtn.classList.add('velu-msg-action-active');
|
|
245
|
+
dislikeBtn.classList.remove('velu-msg-action-active');
|
|
246
|
+
state.feedback[messageId] = 'up';
|
|
247
|
+
saveFeedbackState();
|
|
248
|
+
submitFeedback(messageId, 'up').catch(() => {});
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
dislikeBtn.onclick = () => {
|
|
252
|
+
if (!messageId) return;
|
|
253
|
+
const isActive = dislikeBtn.classList.contains('velu-msg-action-active');
|
|
254
|
+
if (isActive) {
|
|
255
|
+
// Retract
|
|
256
|
+
dislikeBtn.classList.remove('velu-msg-action-active');
|
|
257
|
+
delete state.feedback[messageId];
|
|
258
|
+
saveFeedbackState();
|
|
259
|
+
retractFeedback(messageId).catch(() => {});
|
|
260
|
+
} else {
|
|
261
|
+
// Submit down
|
|
262
|
+
dislikeBtn.classList.add('velu-msg-action-active');
|
|
263
|
+
likeBtn.classList.remove('velu-msg-action-active');
|
|
264
|
+
state.feedback[messageId] = 'down';
|
|
265
|
+
saveFeedbackState();
|
|
266
|
+
submitFeedback(messageId, 'down').catch(() => {});
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Copy button
|
|
272
|
+
const copyBtn = actions.querySelector('.velu-msg-copy') as HTMLElement;
|
|
197
273
|
if (copyBtn) {
|
|
198
|
-
|
|
274
|
+
copyBtn.onclick = () => {
|
|
199
275
|
navigator.clipboard.writeText(content);
|
|
200
|
-
|
|
201
|
-
|
|
276
|
+
copyBtn.classList.add('velu-msg-action-active');
|
|
277
|
+
copyBtn.title = 'Copied!';
|
|
278
|
+
setTimeout(() => { copyBtn.classList.remove('velu-msg-action-active'); copyBtn.title = 'Copy'; }, 1500);
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Regenerate button
|
|
283
|
+
const regenBtn = actions.querySelector('.velu-msg-regenerate') as HTMLElement;
|
|
284
|
+
if (regenBtn) {
|
|
285
|
+
regenBtn.onclick = () => {
|
|
286
|
+
const allMsgs = messagesEl.querySelectorAll('.velu-msg');
|
|
287
|
+
let lastUserText = '';
|
|
288
|
+
for (let i = allMsgs.length - 1; i >= 0; i--) {
|
|
289
|
+
if (allMsgs[i].classList.contains('velu-msg-user')) {
|
|
290
|
+
const bubble = allMsgs[i].querySelector('.velu-msg-bubble');
|
|
291
|
+
if (bubble) lastUserText = bubble.textContent || '';
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (lastUserText) {
|
|
296
|
+
msgDiv.remove();
|
|
297
|
+
saveState();
|
|
298
|
+
addThinking();
|
|
299
|
+
bootstrap()
|
|
300
|
+
.then(() =>
|
|
301
|
+
fetch(API_BASE + '/messages', {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
headers: { 'Content-Type': 'application/json' },
|
|
304
|
+
credentials: 'include',
|
|
305
|
+
body: JSON.stringify({
|
|
306
|
+
message: lastUserText,
|
|
307
|
+
conversation_id: state.conversationId,
|
|
308
|
+
}),
|
|
309
|
+
})
|
|
310
|
+
)
|
|
311
|
+
.then((r) => {
|
|
312
|
+
if (r.status === 429) {
|
|
313
|
+
removeThinking();
|
|
314
|
+
addMessage('assistant', 'Rate limited. Please wait a moment and try again.');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
return r.json();
|
|
318
|
+
})
|
|
319
|
+
.then((data: any) => {
|
|
320
|
+
if (!data) return;
|
|
321
|
+
if (data.conversation_id) state.conversationId = data.conversation_id;
|
|
322
|
+
if (data.conversation_token) state.conversationToken = data.conversation_token;
|
|
323
|
+
saveState();
|
|
324
|
+
if (!state.eventSource || state.eventSource.readyState === 2) {
|
|
325
|
+
connectSSE();
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
.catch(() => {
|
|
329
|
+
removeThinking();
|
|
330
|
+
addMessage('assistant', 'Failed to connect. Please try again.');
|
|
331
|
+
});
|
|
332
|
+
}
|
|
202
333
|
};
|
|
203
334
|
}
|
|
204
335
|
}
|
|
@@ -233,7 +364,7 @@ function initAssistant() {
|
|
|
233
364
|
const data = JSON.parse(e.data);
|
|
234
365
|
const msg = data.message || data;
|
|
235
366
|
if (msg.seq) state.lastSeq = msg.seq;
|
|
236
|
-
addMessage('assistant', msg.content || '', msg.citations || []);
|
|
367
|
+
addMessage('assistant', msg.content || '', msg.citations || [], msg.id);
|
|
237
368
|
} catch {}
|
|
238
369
|
});
|
|
239
370
|
|
|
@@ -342,10 +473,64 @@ function initAssistant() {
|
|
|
342
473
|
const savedConvId = sessionStorage.getItem('velu-conv-id');
|
|
343
474
|
const savedConvToken = sessionStorage.getItem('velu-conv-token');
|
|
344
475
|
const savedSeq = sessionStorage.getItem('velu-last-seq');
|
|
476
|
+
const savedFeedback = sessionStorage.getItem('velu-feedback');
|
|
345
477
|
if (savedConvId) state.conversationId = savedConvId;
|
|
346
478
|
if (savedConvToken) state.conversationToken = savedConvToken;
|
|
347
479
|
if (savedSeq) state.lastSeq = parseInt(savedSeq, 10) || 0;
|
|
348
|
-
if (
|
|
480
|
+
if (savedFeedback) try { state.feedback = JSON.parse(savedFeedback); } catch {}
|
|
481
|
+
if (savedMessages) {
|
|
482
|
+
messagesEl.innerHTML = savedMessages;
|
|
483
|
+
// Re-bind action handlers on restored messages
|
|
484
|
+
messagesEl.querySelectorAll('.velu-msg-assistant').forEach((msgDiv) => {
|
|
485
|
+
const messageId = msgDiv.getAttribute('data-message-id');
|
|
486
|
+
const mid = messageId ? parseInt(messageId, 10) : null;
|
|
487
|
+
const likeBtn = msgDiv.querySelector('.velu-msg-like') as HTMLElement | null;
|
|
488
|
+
const dislikeBtn = msgDiv.querySelector('.velu-msg-dislike') as HTMLElement | null;
|
|
489
|
+
const copyBtn = msgDiv.querySelector('.velu-msg-copy') as HTMLElement | null;
|
|
490
|
+
const bubble = msgDiv.querySelector('.velu-msg-bubble') as HTMLElement | null;
|
|
491
|
+
|
|
492
|
+
if (likeBtn && dislikeBtn && mid) {
|
|
493
|
+
likeBtn.onclick = () => {
|
|
494
|
+
const isActive = likeBtn.classList.contains('velu-msg-action-active');
|
|
495
|
+
if (isActive) {
|
|
496
|
+
likeBtn.classList.remove('velu-msg-action-active');
|
|
497
|
+
delete state.feedback[mid];
|
|
498
|
+
saveFeedbackState();
|
|
499
|
+
retractFeedback(mid).catch(() => {});
|
|
500
|
+
} else {
|
|
501
|
+
likeBtn.classList.add('velu-msg-action-active');
|
|
502
|
+
dislikeBtn.classList.remove('velu-msg-action-active');
|
|
503
|
+
state.feedback[mid] = 'up';
|
|
504
|
+
saveFeedbackState();
|
|
505
|
+
submitFeedback(mid, 'up').catch(() => {});
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
dislikeBtn.onclick = () => {
|
|
509
|
+
const isActive = dislikeBtn.classList.contains('velu-msg-action-active');
|
|
510
|
+
if (isActive) {
|
|
511
|
+
dislikeBtn.classList.remove('velu-msg-action-active');
|
|
512
|
+
delete state.feedback[mid];
|
|
513
|
+
saveFeedbackState();
|
|
514
|
+
retractFeedback(mid).catch(() => {});
|
|
515
|
+
} else {
|
|
516
|
+
dislikeBtn.classList.add('velu-msg-action-active');
|
|
517
|
+
likeBtn.classList.remove('velu-msg-action-active');
|
|
518
|
+
state.feedback[mid] = 'down';
|
|
519
|
+
saveFeedbackState();
|
|
520
|
+
submitFeedback(mid, 'down').catch(() => {});
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (copyBtn && bubble) {
|
|
525
|
+
copyBtn.onclick = () => {
|
|
526
|
+
navigator.clipboard.writeText(bubble.textContent || '');
|
|
527
|
+
copyBtn.classList.add('velu-msg-action-active');
|
|
528
|
+
copyBtn.title = 'Copied!';
|
|
529
|
+
setTimeout(() => { copyBtn.classList.remove('velu-msg-action-active'); copyBtn.title = 'Copy'; }, 1500);
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
349
534
|
if (savedExpanded === '1') {
|
|
350
535
|
state.expanded = true;
|
|
351
536
|
panel.classList.add('velu-assistant-expanded');
|
package/src/engine/lib/velu.ts
CHANGED
|
@@ -1032,3 +1032,14 @@ export function getSiteOrigin(src?: VeluConfigSource): string {
|
|
|
1032
1032
|
|
|
1033
1033
|
return 'http://localhost:4321';
|
|
1034
1034
|
}
|
|
1035
|
+
|
|
1036
|
+
export function getCliVersion(): string {
|
|
1037
|
+
try {
|
|
1038
|
+
const constPath = resolve(process.cwd(), 'public', 'const.json');
|
|
1039
|
+
if (existsSync(constPath)) {
|
|
1040
|
+
const parsed = JSON.parse(readFileSync(constPath, 'utf-8'));
|
|
1041
|
+
if (typeof parsed.version === 'string') return parsed.version;
|
|
1042
|
+
}
|
|
1043
|
+
} catch {}
|
|
1044
|
+
return 'unknown';
|
|
1045
|
+
}
|