@aravindc26/velu 0.5.0 → 0.6.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/package.json +1 -1
- package/src/build.ts +768 -2
package/package.json
CHANGED
package/src/build.ts
CHANGED
|
@@ -341,8 +341,9 @@ export default defineConfig({
|
|
|
341
341
|
components: {
|
|
342
342
|
Sidebar: './src/components/Sidebar.astro',
|
|
343
343
|
PageTitle: './src/components/PageTitle.astro',
|
|
344
|
+
Footer: './src/components/Footer.astro',
|
|
344
345
|
},
|
|
345
|
-
customCss: ['./src/styles/velu-theme.css', './src/styles/tabs.css'],${expressiveCodeConfig}
|
|
346
|
+
customCss: ['./src/styles/velu-theme.css', './src/styles/tabs.css', './src/styles/assistant.css'],${expressiveCodeConfig}
|
|
346
347
|
sidebar: getSidebar(),
|
|
347
348
|
}),
|
|
348
349
|
],
|
|
@@ -513,10 +514,419 @@ const title = Astro.locals.starlightRoute.entry.data.title;
|
|
|
513
514
|
}
|
|
514
515
|
})();
|
|
515
516
|
</script>
|
|
517
|
+
|
|
518
|
+
<!-- AI Assistant Widget -->
|
|
519
|
+
<div class="velu-ask-bar" id="veluAskBar">
|
|
520
|
+
<div class="velu-ask-bar-inner">
|
|
521
|
+
<input type="text" class="velu-ask-input" id="veluAskInput" placeholder="Ask a question..." autocomplete="off" />
|
|
522
|
+
<button class="velu-ask-submit" id="veluAskSubmit" aria-label="Send">
|
|
523
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
|
524
|
+
</button>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
<div class="velu-assistant-panel velu-panel-closed" id="veluAssistantPanel">
|
|
529
|
+
<div class="velu-assistant-header">
|
|
530
|
+
<span class="velu-assistant-title">Assistant</span>
|
|
531
|
+
<div class="velu-assistant-actions">
|
|
532
|
+
<button class="velu-assistant-action" data-velu-action="expand" title="Expand" aria-label="Expand assistant" type="button">
|
|
533
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
|
534
|
+
</button>
|
|
535
|
+
<button class="velu-assistant-action" data-velu-action="reset" title="New chat" aria-label="New chat" type="button">
|
|
536
|
+
<svg width="14" height="14" 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"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>
|
|
537
|
+
</button>
|
|
538
|
+
<button class="velu-assistant-action" data-velu-action="close" title="Close" aria-label="Close assistant" type="button">
|
|
539
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
540
|
+
</button>
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
<div class="velu-assistant-messages" id="veluAssistantMessages"></div>
|
|
544
|
+
<div class="velu-assistant-input-area">
|
|
545
|
+
<input type="text" class="velu-assistant-chat-input" id="veluAssistantChatInput" placeholder="Ask a question..." autocomplete="off" />
|
|
546
|
+
<button class="velu-assistant-send" id="veluAssistantSend" aria-label="Send">
|
|
547
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M3.478 2.405a.75.75 0 00-.926.94l2.432 7.905H13.5a.75.75 0 010 1.5H4.984l-2.432 7.905a.75.75 0 00.926.94l18-8.5a.75.75 0 000-1.38l-18-8.5z"/></svg>
|
|
548
|
+
</button>
|
|
549
|
+
</div>
|
|
550
|
+
</div>
|
|
551
|
+
|
|
552
|
+
<script is:inline>
|
|
553
|
+
(function veluAssistant() {
|
|
554
|
+
var API_BASE = 'https://api.getvelu.com/api/v1/public/ai-assistant';
|
|
555
|
+
var state = {
|
|
556
|
+
conversationId: null,
|
|
557
|
+
conversationToken: null,
|
|
558
|
+
lastSeq: 0,
|
|
559
|
+
eventSource: null,
|
|
560
|
+
expanded: false,
|
|
561
|
+
bootstrapped: false
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
var askBar = document.getElementById('veluAskBar');
|
|
565
|
+
var askInput = document.getElementById('veluAskInput');
|
|
566
|
+
var askSubmit = document.getElementById('veluAskSubmit');
|
|
567
|
+
var panel = document.getElementById('veluAssistantPanel');
|
|
568
|
+
var messagesEl = document.getElementById('veluAssistantMessages');
|
|
569
|
+
var chatInput = document.getElementById('veluAssistantChatInput');
|
|
570
|
+
var sendBtn = document.getElementById('veluAssistantSend');
|
|
571
|
+
var closeBtn = document.getElementById('veluAssistantClose');
|
|
572
|
+
var expandBtn = document.getElementById('veluAssistantExpand');
|
|
573
|
+
var newChatBtn = document.getElementById('veluAssistantNewChat');
|
|
574
|
+
|
|
575
|
+
if (!askBar || !panel) return;
|
|
576
|
+
|
|
577
|
+
if (panel.parentElement !== document.body) {
|
|
578
|
+
document.body.appendChild(panel);
|
|
579
|
+
}
|
|
580
|
+
if (askBar.parentElement !== document.body) {
|
|
581
|
+
document.body.appendChild(askBar);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function saveState() {
|
|
585
|
+
try {
|
|
586
|
+
sessionStorage.setItem('velu-panel-open', isPanelOpen() ? '1' : '');
|
|
587
|
+
sessionStorage.setItem('velu-panel-expanded', state.expanded ? '1' : '');
|
|
588
|
+
sessionStorage.setItem('velu-panel-messages', messagesEl.innerHTML);
|
|
589
|
+
sessionStorage.setItem('velu-conv-id', state.conversationId || '');
|
|
590
|
+
sessionStorage.setItem('velu-conv-token', state.conversationToken || '');
|
|
591
|
+
sessionStorage.setItem('velu-last-seq', String(state.lastSeq));
|
|
592
|
+
} catch(e) {}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function openPanel() {
|
|
596
|
+
panel.classList.remove('velu-panel-closed');
|
|
597
|
+
askBar.classList.add('velu-ask-bar-hidden');
|
|
598
|
+
document.documentElement.classList.add('velu-assistant-open');
|
|
599
|
+
chatInput.focus();
|
|
600
|
+
saveState();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function closePanel() {
|
|
604
|
+
panel.classList.add('velu-panel-closed');
|
|
605
|
+
askBar.classList.remove('velu-ask-bar-hidden');
|
|
606
|
+
document.documentElement.classList.remove('velu-assistant-open');
|
|
607
|
+
document.documentElement.classList.remove('velu-assistant-wide');
|
|
608
|
+
if (state.eventSource) { state.eventSource.close(); state.eventSource = null; }
|
|
609
|
+
saveState();
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function resetChat() {
|
|
613
|
+
state.conversationId = null;
|
|
614
|
+
state.conversationToken = null;
|
|
615
|
+
state.lastSeq = 0;
|
|
616
|
+
if (state.eventSource) { state.eventSource.close(); state.eventSource = null; }
|
|
617
|
+
messagesEl.innerHTML = '';
|
|
618
|
+
chatInput.value = '';
|
|
619
|
+
chatInput.focus();
|
|
620
|
+
saveState();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function toggleExpand() {
|
|
624
|
+
state.expanded = !state.expanded;
|
|
625
|
+
panel.classList.toggle('velu-assistant-expanded', state.expanded);
|
|
626
|
+
document.documentElement.classList.toggle('velu-assistant-wide', state.expanded);
|
|
627
|
+
saveState();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Expose to inline onclick handlers
|
|
631
|
+
window._veluClosePanel = closePanel;
|
|
632
|
+
window._veluResetChat = resetChat;
|
|
633
|
+
window._veluToggleExpand = toggleExpand;
|
|
634
|
+
|
|
635
|
+
function bootstrap() {
|
|
636
|
+
if (state.bootstrapped) return Promise.resolve();
|
|
637
|
+
return fetch(API_BASE + '/bootstrap', { credentials: 'include' })
|
|
638
|
+
.then(function(r) { return r.json(); })
|
|
639
|
+
.then(function(d) { state.bootstrapped = true; })
|
|
640
|
+
.catch(function() {});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function isPanelOpen() {
|
|
644
|
+
return !panel.classList.contains('velu-panel-closed');
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function addMessage(role, content, citations) {
|
|
648
|
+
var msgDiv = document.createElement('div');
|
|
649
|
+
msgDiv.className = 'velu-msg velu-msg-' + role;
|
|
650
|
+
var bubble = document.createElement('div');
|
|
651
|
+
bubble.className = 'velu-msg-bubble velu-msg-bubble-' + role;
|
|
652
|
+
bubble.innerHTML = formatContent(content, citations || []);
|
|
653
|
+
msgDiv.appendChild(bubble);
|
|
654
|
+
|
|
655
|
+
if (role === 'assistant' && citations && citations.length > 0) {
|
|
656
|
+
var citDiv = document.createElement('div');
|
|
657
|
+
citDiv.className = 'velu-msg-citations';
|
|
658
|
+
citations.forEach(function(c, i) {
|
|
659
|
+
var a = document.createElement('a');
|
|
660
|
+
a.href = c.url || c.route_path || '#';
|
|
661
|
+
a.className = 'velu-citation-link';
|
|
662
|
+
a.textContent = '[' + (i + 1) + '] ' + (c.title || c.route_path || 'Source');
|
|
663
|
+
a.target = '_blank';
|
|
664
|
+
citDiv.appendChild(a);
|
|
665
|
+
});
|
|
666
|
+
msgDiv.appendChild(citDiv);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (role === 'assistant') {
|
|
670
|
+
var actions = document.createElement('div');
|
|
671
|
+
actions.className = 'velu-msg-actions';
|
|
672
|
+
actions.innerHTML = '<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>'
|
|
673
|
+
+ '<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>'
|
|
674
|
+
+ '<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>'
|
|
675
|
+
+ '<button class="velu-msg-action velu-msg-retry" title="Retry"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg></button>';
|
|
676
|
+
msgDiv.appendChild(actions);
|
|
677
|
+
|
|
678
|
+
var copyBtn = actions.querySelector('.velu-msg-copy');
|
|
679
|
+
if (copyBtn) {
|
|
680
|
+
copyBtn.onclick = function() {
|
|
681
|
+
navigator.clipboard.writeText(content);
|
|
682
|
+
copyBtn.title = 'Copied!';
|
|
683
|
+
setTimeout(function() { copyBtn.title = 'Copy'; }, 1500);
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
messagesEl.appendChild(msgDiv);
|
|
689
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
690
|
+
saveState();
|
|
691
|
+
return bubble;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function formatContent(text, citations) {
|
|
695
|
+
var html = text
|
|
696
|
+
.replace(/&/g, '&')
|
|
697
|
+
.replace(/</g, '<')
|
|
698
|
+
.replace(/>/g, '>')
|
|
699
|
+
.replace(/\\n/g, '<br>')
|
|
700
|
+
.replace(/\`([^\`]+)\`/g, '<code>$1</code>')
|
|
701
|
+
.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');
|
|
702
|
+
html = html.replace(/\\[(\\d+)\\]/g, function(m, n) {
|
|
703
|
+
var idx = parseInt(n) - 1;
|
|
704
|
+
var c = citations[idx];
|
|
705
|
+
if (c) {
|
|
706
|
+
return '<a href="' + (c.url || c.route_path || '#') + '" class="velu-citation-ref" target="_blank">[' + n + ']</a>';
|
|
707
|
+
}
|
|
708
|
+
return m;
|
|
709
|
+
});
|
|
710
|
+
return html;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function addThinking() {
|
|
714
|
+
var div = document.createElement('div');
|
|
715
|
+
div.className = 'velu-msg velu-msg-assistant';
|
|
716
|
+
div.id = 'veluThinking';
|
|
717
|
+
div.innerHTML = '<div class="velu-msg-bubble velu-msg-bubble-assistant"><span class="velu-thinking-dots"><span></span><span></span><span></span></span></div>';
|
|
718
|
+
messagesEl.appendChild(div);
|
|
719
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function removeThinking() {
|
|
723
|
+
var el = document.getElementById('veluThinking');
|
|
724
|
+
if (el) el.remove();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function connectSSE() {
|
|
728
|
+
if (state.eventSource) state.eventSource.close();
|
|
729
|
+
var url = API_BASE + '/conversations/' + state.conversationId + '/events?after_seq=' + state.lastSeq;
|
|
730
|
+
state.eventSource = new EventSource(url);
|
|
731
|
+
|
|
732
|
+
state.eventSource.addEventListener('assistant.completed', function(e) {
|
|
733
|
+
removeThinking();
|
|
734
|
+
try {
|
|
735
|
+
var data = JSON.parse(e.data);
|
|
736
|
+
var msg = data.message || data;
|
|
737
|
+
if (msg.seq) state.lastSeq = msg.seq;
|
|
738
|
+
addMessage('assistant', msg.content || '', msg.citations || []);
|
|
739
|
+
} catch(err) {}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
state.eventSource.addEventListener('assistant.error', function(e) {
|
|
743
|
+
removeThinking();
|
|
744
|
+
try {
|
|
745
|
+
var data = JSON.parse(e.data);
|
|
746
|
+
addMessage('assistant', data.error || 'Something went wrong. Please try again.', []);
|
|
747
|
+
} catch(err) {
|
|
748
|
+
addMessage('assistant', 'Something went wrong. Please try again.', []);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
state.eventSource.onerror = function() {};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function sendMessage(text) {
|
|
756
|
+
if (!text.trim()) return;
|
|
757
|
+
addMessage('user', text);
|
|
758
|
+
addThinking();
|
|
759
|
+
|
|
760
|
+
bootstrap().then(function() {
|
|
761
|
+
return fetch(API_BASE + '/messages', {
|
|
762
|
+
method: 'POST',
|
|
763
|
+
headers: { 'Content-Type': 'application/json' },
|
|
764
|
+
credentials: 'include',
|
|
765
|
+
body: JSON.stringify({
|
|
766
|
+
message: text,
|
|
767
|
+
conversation_id: state.conversationId
|
|
768
|
+
})
|
|
769
|
+
});
|
|
770
|
+
}).then(function(r) {
|
|
771
|
+
if (r.status === 429) { removeThinking(); addMessage('assistant', 'Rate limited. Please wait a moment and try again.', []); return; }
|
|
772
|
+
return r.json();
|
|
773
|
+
}).then(function(data) {
|
|
774
|
+
if (!data) return;
|
|
775
|
+
if (data.conversation_id) state.conversationId = data.conversation_id;
|
|
776
|
+
if (data.conversation_token) state.conversationToken = data.conversation_token;
|
|
777
|
+
saveState();
|
|
778
|
+
if (!state.eventSource || state.eventSource.readyState === 2) {
|
|
779
|
+
connectSSE();
|
|
780
|
+
}
|
|
781
|
+
}).catch(function() {
|
|
782
|
+
removeThinking();
|
|
783
|
+
addMessage('assistant', 'Failed to connect. Please try again.', []);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function handleAskSubmit() {
|
|
788
|
+
var text = askInput.value.trim();
|
|
789
|
+
if (!text) return;
|
|
790
|
+
askInput.value = '';
|
|
791
|
+
openPanel();
|
|
792
|
+
sendMessage(text);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function handleChatSubmit() {
|
|
796
|
+
var text = chatInput.value.trim();
|
|
797
|
+
if (!text) return;
|
|
798
|
+
chatInput.value = '';
|
|
799
|
+
sendMessage(text);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
askInput.onkeydown = function(e) { if (e.key === 'Enter') handleAskSubmit(); };
|
|
803
|
+
askSubmit.onclick = handleAskSubmit;
|
|
804
|
+
chatInput.onkeydown = function(e) { if (e.key === 'Enter') handleChatSubmit(); };
|
|
805
|
+
sendBtn.onclick = handleChatSubmit;
|
|
806
|
+
|
|
807
|
+
panel.addEventListener('click', function(e) {
|
|
808
|
+
var actionBtn = e.target.closest('[data-velu-action]');
|
|
809
|
+
if (!actionBtn) return;
|
|
810
|
+
var action = actionBtn.getAttribute('data-velu-action');
|
|
811
|
+
if (action === 'close') {
|
|
812
|
+
closePanel();
|
|
813
|
+
} else if (action === 'expand') {
|
|
814
|
+
toggleExpand();
|
|
815
|
+
} else if (action === 'reset') {
|
|
816
|
+
resetChat();
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
document.addEventListener('click', function(e) {
|
|
821
|
+
var actionBtn = e.target.closest('[data-velu-action]');
|
|
822
|
+
if (!actionBtn) return;
|
|
823
|
+
var action = actionBtn.getAttribute('data-velu-action');
|
|
824
|
+
if (action === 'close') {
|
|
825
|
+
closePanel();
|
|
826
|
+
} else if (action === 'expand') {
|
|
827
|
+
toggleExpand();
|
|
828
|
+
} else if (action === 'reset') {
|
|
829
|
+
resetChat();
|
|
830
|
+
}
|
|
831
|
+
}, true);
|
|
832
|
+
|
|
833
|
+
// Hide ask bar only when user scrolls to the very bottom
|
|
834
|
+
window.addEventListener('scroll', function() {
|
|
835
|
+
if (isPanelOpen()) return;
|
|
836
|
+
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
837
|
+
var docHeight = document.documentElement.scrollHeight;
|
|
838
|
+
var winHeight = window.innerHeight;
|
|
839
|
+
if (docHeight <= winHeight + 10) return; // short pages: always show
|
|
840
|
+
if (docHeight - scrollTop - winHeight < 60) {
|
|
841
|
+
askBar.classList.add('velu-ask-bar-hidden');
|
|
842
|
+
} else {
|
|
843
|
+
askBar.classList.remove('velu-ask-bar-hidden');
|
|
844
|
+
}
|
|
845
|
+
}, { passive: true });
|
|
846
|
+
|
|
847
|
+
document.onkeydown = function(e) {
|
|
848
|
+
if (e.key === 'Escape' && isPanelOpen()) { closePanel(); }
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// Restore panel state from sessionStorage on page load
|
|
852
|
+
try {
|
|
853
|
+
var savedOpen = sessionStorage.getItem('velu-panel-open');
|
|
854
|
+
var savedExpanded = sessionStorage.getItem('velu-panel-expanded');
|
|
855
|
+
var savedMessages = sessionStorage.getItem('velu-panel-messages');
|
|
856
|
+
var savedConvId = sessionStorage.getItem('velu-conv-id');
|
|
857
|
+
var savedConvToken = sessionStorage.getItem('velu-conv-token');
|
|
858
|
+
var savedSeq = sessionStorage.getItem('velu-last-seq');
|
|
859
|
+
if (savedConvId) state.conversationId = savedConvId;
|
|
860
|
+
if (savedConvToken) state.conversationToken = savedConvToken;
|
|
861
|
+
if (savedSeq) state.lastSeq = parseInt(savedSeq, 10) || 0;
|
|
862
|
+
if (savedMessages) messagesEl.innerHTML = savedMessages;
|
|
863
|
+
if (savedExpanded === '1') {
|
|
864
|
+
state.expanded = true;
|
|
865
|
+
panel.classList.add('velu-assistant-expanded');
|
|
866
|
+
document.documentElement.classList.add('velu-assistant-wide');
|
|
867
|
+
}
|
|
868
|
+
if (savedOpen === '1') {
|
|
869
|
+
openPanel();
|
|
870
|
+
if (state.conversationId) connectSSE();
|
|
871
|
+
}
|
|
872
|
+
} catch(e) {}
|
|
873
|
+
|
|
874
|
+
bootstrap();
|
|
875
|
+
})();
|
|
876
|
+
</script>
|
|
516
877
|
`;
|
|
517
878
|
writeFileSync(join(outDir, "src", "components", "PageTitle.astro"), pageTitleComponent, "utf-8");
|
|
518
879
|
console.log("📋 Generated page title component");
|
|
519
880
|
|
|
881
|
+
// ── 7b. Generate Footer.astro — Powered by Velu ────────────────────────
|
|
882
|
+
const footerComponent = `---
|
|
883
|
+
import EditLink from 'virtual:starlight/components/EditLink';
|
|
884
|
+
import LastUpdated from 'virtual:starlight/components/LastUpdated';
|
|
885
|
+
import Pagination from 'virtual:starlight/components/Pagination';
|
|
886
|
+
---
|
|
887
|
+
|
|
888
|
+
<footer class="sl-flex">
|
|
889
|
+
<div class="meta sl-flex">
|
|
890
|
+
<EditLink />
|
|
891
|
+
<LastUpdated />
|
|
892
|
+
</div>
|
|
893
|
+
<Pagination />
|
|
894
|
+
<div class="velu-powered-by">
|
|
895
|
+
<a href="https://getvelu.com" target="_blank" rel="noopener noreferrer">Powered by Velu</a>
|
|
896
|
+
</div>
|
|
897
|
+
</footer>
|
|
898
|
+
|
|
899
|
+
<style>
|
|
900
|
+
footer {
|
|
901
|
+
flex-direction: column;
|
|
902
|
+
gap: 1.5rem;
|
|
903
|
+
}
|
|
904
|
+
.meta {
|
|
905
|
+
gap: 0.75rem;
|
|
906
|
+
align-items: center;
|
|
907
|
+
flex-wrap: wrap;
|
|
908
|
+
justify-content: space-between;
|
|
909
|
+
}
|
|
910
|
+
.velu-powered-by {
|
|
911
|
+
text-align: right;
|
|
912
|
+
padding: 1rem 2rem 0.5rem 0;
|
|
913
|
+
}
|
|
914
|
+
.velu-powered-by a {
|
|
915
|
+
font-size: 1.4rem;
|
|
916
|
+
font-weight: 500;
|
|
917
|
+
letter-spacing: 0.02em;
|
|
918
|
+
color: rgba(160, 165, 180, 0.45);
|
|
919
|
+
text-decoration: none;
|
|
920
|
+
transition: color 0.25s ease;
|
|
921
|
+
}
|
|
922
|
+
.velu-powered-by a:hover {
|
|
923
|
+
color: rgba(220, 225, 240, 0.95);
|
|
924
|
+
}
|
|
925
|
+
</style>
|
|
926
|
+
`;
|
|
927
|
+
writeFileSync(join(outDir, "src", "components", "Footer.astro"), footerComponent, "utf-8");
|
|
928
|
+
console.log("📋 Generated footer component");
|
|
929
|
+
|
|
520
930
|
// ── 8. Generate tabs.css ──────────────────────────────────────────────────
|
|
521
931
|
const tabsCss = `/* ── Velu sidebar tabs ─────────────────────────────────────────────────── */
|
|
522
932
|
|
|
@@ -705,7 +1115,363 @@ const title = Astro.locals.starlightRoute.entry.data.title;
|
|
|
705
1115
|
writeFileSync(join(outDir, "src", "styles", "tabs.css"), tabsCss, "utf-8");
|
|
706
1116
|
console.log("🎨 Generated tabs.css");
|
|
707
1117
|
|
|
708
|
-
// ── 9.
|
|
1118
|
+
// ── 9. Generate assistant.css ───────────────────────────────────────────
|
|
1119
|
+
const assistantCss = `/* ── Velu AI Assistant ─────────────────────────────────────────────────── */
|
|
1120
|
+
|
|
1121
|
+
/* Fixed bottom ask bar */
|
|
1122
|
+
.velu-ask-bar {
|
|
1123
|
+
position: fixed;
|
|
1124
|
+
bottom: 1.5rem;
|
|
1125
|
+
left: 50%;
|
|
1126
|
+
transform: translateX(-50%);
|
|
1127
|
+
z-index: 200;
|
|
1128
|
+
width: 100%;
|
|
1129
|
+
max-width: 36rem;
|
|
1130
|
+
padding: 0 1rem;
|
|
1131
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
.velu-ask-bar-hidden {
|
|
1135
|
+
opacity: 0;
|
|
1136
|
+
pointer-events: none;
|
|
1137
|
+
transform: translateX(-50%) translateY(1rem);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.velu-ask-bar-inner {
|
|
1141
|
+
display: flex;
|
|
1142
|
+
align-items: center;
|
|
1143
|
+
gap: 0.5rem;
|
|
1144
|
+
padding: 0.5rem 0.75rem;
|
|
1145
|
+
background: var(--sl-color-bg-nav);
|
|
1146
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
1147
|
+
border-radius: 0.75rem;
|
|
1148
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.velu-ask-icon {
|
|
1152
|
+
flex-shrink: 0;
|
|
1153
|
+
color: var(--sl-color-gray-3);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.velu-ask-input {
|
|
1157
|
+
flex: 1;
|
|
1158
|
+
background: none;
|
|
1159
|
+
border: none;
|
|
1160
|
+
outline: none;
|
|
1161
|
+
font: inherit;
|
|
1162
|
+
font-size: var(--sl-text-sm);
|
|
1163
|
+
color: var(--sl-color-white);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.velu-ask-input::placeholder {
|
|
1167
|
+
color: var(--sl-color-gray-3);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
.velu-ask-submit {
|
|
1172
|
+
flex-shrink: 0;
|
|
1173
|
+
display: flex;
|
|
1174
|
+
align-items: center;
|
|
1175
|
+
justify-content: center;
|
|
1176
|
+
width: 28px;
|
|
1177
|
+
height: 28px;
|
|
1178
|
+
background: var(--sl-color-accent);
|
|
1179
|
+
color: var(--sl-color-accent-high);
|
|
1180
|
+
border: none;
|
|
1181
|
+
border-radius: 50%;
|
|
1182
|
+
cursor: pointer;
|
|
1183
|
+
transition: opacity 0.15s;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
.velu-ask-submit:hover { opacity: 0.85; }
|
|
1187
|
+
|
|
1188
|
+
/* Right-side assistant panel */
|
|
1189
|
+
.velu-assistant-panel {
|
|
1190
|
+
position: fixed;
|
|
1191
|
+
top: var(--sl-nav-height, 3.5rem);
|
|
1192
|
+
right: 0;
|
|
1193
|
+
bottom: 0;
|
|
1194
|
+
width: 22rem;
|
|
1195
|
+
z-index: 50;
|
|
1196
|
+
pointer-events: auto;
|
|
1197
|
+
display: flex;
|
|
1198
|
+
flex-direction: column;
|
|
1199
|
+
background: var(--sl-color-bg);
|
|
1200
|
+
border-left: 1px solid var(--sl-color-gray-5);
|
|
1201
|
+
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.2);
|
|
1202
|
+
transition: width 0.2s;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
.velu-panel-closed { display: none !important; }
|
|
1206
|
+
|
|
1207
|
+
.velu-assistant-expanded { width: 40rem; }
|
|
1208
|
+
|
|
1209
|
+
.velu-assistant-header {
|
|
1210
|
+
display: flex;
|
|
1211
|
+
align-items: center;
|
|
1212
|
+
justify-content: space-between;
|
|
1213
|
+
padding: 0.75rem 1rem;
|
|
1214
|
+
border-bottom: 1px solid var(--sl-color-gray-5);
|
|
1215
|
+
flex-shrink: 0;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.velu-assistant-title {
|
|
1219
|
+
font-weight: 600;
|
|
1220
|
+
font-size: var(--sl-text-base);
|
|
1221
|
+
color: var(--sl-color-white);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
.velu-assistant-actions {
|
|
1225
|
+
display: flex;
|
|
1226
|
+
gap: 0.25rem;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
.velu-assistant-action {
|
|
1230
|
+
display: flex;
|
|
1231
|
+
align-items: center;
|
|
1232
|
+
justify-content: center;
|
|
1233
|
+
width: 28px;
|
|
1234
|
+
height: 28px;
|
|
1235
|
+
background: none;
|
|
1236
|
+
border: none;
|
|
1237
|
+
border-radius: 0.25rem;
|
|
1238
|
+
color: var(--sl-color-gray-3);
|
|
1239
|
+
cursor: pointer;
|
|
1240
|
+
pointer-events: auto;
|
|
1241
|
+
transition: color 0.15s, background-color 0.15s;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.velu-assistant-action:hover {
|
|
1245
|
+
color: var(--sl-color-white);
|
|
1246
|
+
background-color: var(--sl-color-gray-6);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/* Messages area */
|
|
1250
|
+
.velu-assistant-messages {
|
|
1251
|
+
flex: 1;
|
|
1252
|
+
overflow-y: auto;
|
|
1253
|
+
padding: 1rem;
|
|
1254
|
+
display: flex;
|
|
1255
|
+
flex-direction: column;
|
|
1256
|
+
gap: 0.75rem;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.velu-msg {
|
|
1260
|
+
display: flex;
|
|
1261
|
+
flex-direction: column;
|
|
1262
|
+
gap: 0.35rem;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.velu-msg-user { align-items: flex-end; }
|
|
1266
|
+
.velu-msg-assistant { align-items: flex-start; }
|
|
1267
|
+
|
|
1268
|
+
.velu-msg-bubble {
|
|
1269
|
+
max-width: 85%;
|
|
1270
|
+
padding: 0.6rem 0.85rem;
|
|
1271
|
+
border-radius: 0.75rem;
|
|
1272
|
+
font-size: var(--sl-text-sm);
|
|
1273
|
+
line-height: 1.55;
|
|
1274
|
+
word-break: break-word;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.velu-msg-bubble code {
|
|
1278
|
+
background: var(--sl-color-gray-6);
|
|
1279
|
+
padding: 0.1rem 0.3rem;
|
|
1280
|
+
border-radius: 0.2rem;
|
|
1281
|
+
font-size: 0.85em;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
.velu-msg-bubble-user {
|
|
1285
|
+
background: var(--sl-color-accent);
|
|
1286
|
+
color: var(--sl-color-accent-high);
|
|
1287
|
+
border-bottom-right-radius: 0.2rem;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
.velu-msg-bubble-assistant {
|
|
1291
|
+
background: var(--sl-color-gray-6);
|
|
1292
|
+
color: var(--sl-color-white);
|
|
1293
|
+
border-bottom-left-radius: 0.2rem;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/* Citations */
|
|
1297
|
+
.velu-msg-citations {
|
|
1298
|
+
display: flex;
|
|
1299
|
+
flex-wrap: wrap;
|
|
1300
|
+
gap: 0.35rem;
|
|
1301
|
+
padding-left: 0.25rem;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
.velu-citation-link {
|
|
1305
|
+
font-size: var(--sl-text-xs);
|
|
1306
|
+
color: var(--sl-color-accent);
|
|
1307
|
+
text-decoration: none;
|
|
1308
|
+
padding: 0.15rem 0.4rem;
|
|
1309
|
+
background: var(--sl-color-gray-6);
|
|
1310
|
+
border-radius: 0.25rem;
|
|
1311
|
+
transition: background-color 0.15s;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
.velu-citation-link:hover {
|
|
1315
|
+
background: var(--sl-color-gray-5);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
.velu-citation-ref {
|
|
1319
|
+
color: var(--sl-color-accent);
|
|
1320
|
+
text-decoration: none;
|
|
1321
|
+
font-weight: 600;
|
|
1322
|
+
font-size: 0.8em;
|
|
1323
|
+
vertical-align: super;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/* Message actions */
|
|
1327
|
+
.velu-msg-actions {
|
|
1328
|
+
display: flex;
|
|
1329
|
+
gap: 0.15rem;
|
|
1330
|
+
padding-left: 0.25rem;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.velu-msg-action {
|
|
1334
|
+
display: flex;
|
|
1335
|
+
align-items: center;
|
|
1336
|
+
justify-content: center;
|
|
1337
|
+
width: 24px;
|
|
1338
|
+
height: 24px;
|
|
1339
|
+
background: none;
|
|
1340
|
+
border: none;
|
|
1341
|
+
border-radius: 0.25rem;
|
|
1342
|
+
color: var(--sl-color-gray-4);
|
|
1343
|
+
cursor: pointer;
|
|
1344
|
+
transition: color 0.15s, background-color 0.15s;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.velu-msg-action:hover {
|
|
1348
|
+
color: var(--sl-color-white);
|
|
1349
|
+
background-color: var(--sl-color-gray-6);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/* Thinking dots */
|
|
1353
|
+
.velu-thinking-dots {
|
|
1354
|
+
display: inline-flex;
|
|
1355
|
+
gap: 0.3rem;
|
|
1356
|
+
padding: 0.2rem 0;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
.velu-thinking-dots span {
|
|
1360
|
+
width: 6px;
|
|
1361
|
+
height: 6px;
|
|
1362
|
+
border-radius: 50%;
|
|
1363
|
+
background: var(--sl-color-gray-3);
|
|
1364
|
+
animation: veluDotPulse 1.2s infinite;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
.velu-thinking-dots span:nth-child(2) { animation-delay: 0.2s; }
|
|
1368
|
+
.velu-thinking-dots span:nth-child(3) { animation-delay: 0.4s; }
|
|
1369
|
+
|
|
1370
|
+
@keyframes veluDotPulse {
|
|
1371
|
+
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
|
1372
|
+
40% { opacity: 1; transform: scale(1); }
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/* Chat input area */
|
|
1376
|
+
.velu-assistant-input-area {
|
|
1377
|
+
display: flex;
|
|
1378
|
+
align-items: center;
|
|
1379
|
+
gap: 0.5rem;
|
|
1380
|
+
padding: 0.75rem 1rem;
|
|
1381
|
+
border-top: 1px solid var(--sl-color-gray-5);
|
|
1382
|
+
flex-shrink: 0;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
.velu-assistant-chat-input {
|
|
1386
|
+
flex: 1;
|
|
1387
|
+
background: var(--sl-color-gray-6);
|
|
1388
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
1389
|
+
border-radius: 0.5rem;
|
|
1390
|
+
padding: 0.5rem 0.75rem;
|
|
1391
|
+
font: inherit;
|
|
1392
|
+
font-size: var(--sl-text-sm);
|
|
1393
|
+
color: var(--sl-color-white);
|
|
1394
|
+
outline: none;
|
|
1395
|
+
transition: border-color 0.15s;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
.velu-assistant-chat-input:focus {
|
|
1399
|
+
border-color: var(--sl-color-accent);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
.velu-assistant-chat-input::placeholder {
|
|
1403
|
+
color: var(--sl-color-gray-3);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.velu-assistant-send {
|
|
1407
|
+
flex-shrink: 0;
|
|
1408
|
+
display: flex;
|
|
1409
|
+
align-items: center;
|
|
1410
|
+
justify-content: center;
|
|
1411
|
+
width: 32px;
|
|
1412
|
+
height: 32px;
|
|
1413
|
+
background: var(--sl-color-accent);
|
|
1414
|
+
color: var(--sl-color-accent-high);
|
|
1415
|
+
border: none;
|
|
1416
|
+
border-radius: 50%;
|
|
1417
|
+
cursor: pointer;
|
|
1418
|
+
transition: opacity 0.15s;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
.velu-assistant-send:hover { opacity: 0.85; }
|
|
1422
|
+
|
|
1423
|
+
/* Squeeze page layout when panel is open */
|
|
1424
|
+
html.velu-assistant-open body {
|
|
1425
|
+
margin-right: 22rem;
|
|
1426
|
+
transition: margin-right 0.25s ease;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
html.velu-assistant-wide body {
|
|
1430
|
+
margin-right: 40rem;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
html.velu-assistant-open .header {
|
|
1434
|
+
padding-right: 22rem;
|
|
1435
|
+
transition: padding-right 0.25s ease;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
html.velu-assistant-wide .header {
|
|
1439
|
+
padding-right: 40rem;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
html.velu-assistant-open .velu-ask-bar {
|
|
1443
|
+
right: 22rem;
|
|
1444
|
+
left: auto;
|
|
1445
|
+
transform: none;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
html.velu-assistant-wide .velu-ask-bar {
|
|
1449
|
+
right: 40rem;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/* Responsive */
|
|
1453
|
+
@media (max-width: 50rem) {
|
|
1454
|
+
.velu-assistant-panel {
|
|
1455
|
+
width: 100%;
|
|
1456
|
+
}
|
|
1457
|
+
.velu-assistant-expanded {
|
|
1458
|
+
width: 100%;
|
|
1459
|
+
}
|
|
1460
|
+
.velu-ask-bar {
|
|
1461
|
+
max-width: calc(100% - 2rem);
|
|
1462
|
+
}
|
|
1463
|
+
html.velu-assistant-open body {
|
|
1464
|
+
margin-right: 0;
|
|
1465
|
+
}
|
|
1466
|
+
html.velu-assistant-wide body {
|
|
1467
|
+
margin-right: 0;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
`;
|
|
1471
|
+
writeFileSync(join(outDir, "src", "styles", "assistant.css"), assistantCss, "utf-8");
|
|
1472
|
+
console.log("🤖 Generated assistant.css");
|
|
1473
|
+
|
|
1474
|
+
// ── 10. Static boilerplate ─────────────────────────────────────────────────
|
|
709
1475
|
const astroPkg = {
|
|
710
1476
|
name: "velu-docs-site",
|
|
711
1477
|
version: "0.0.1",
|