@aravindc26/velu 0.8.0 → 0.9.1

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.
@@ -0,0 +1,6 @@
1
+ import { defineCollection } from 'astro:content';
2
+ import { docsSchema } from '@astrojs/starlight/schema';
3
+
4
+ export const collections = {
5
+ docs: defineCollection({ schema: docsSchema() }),
6
+ };
@@ -0,0 +1,153 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
4
+ // ── Types ───────────────────────────────────────────────────────────────────
5
+
6
+ export interface VeluGroup {
7
+ group: string;
8
+ slug: string;
9
+ icon?: string;
10
+ tag?: string;
11
+ expanded?: boolean;
12
+ pages: (string | VeluGroup)[];
13
+ }
14
+
15
+ export interface VeluTab {
16
+ tab: string;
17
+ slug: string;
18
+ icon?: string;
19
+ href?: string;
20
+ pages?: string[];
21
+ groups?: VeluGroup[];
22
+ }
23
+
24
+ export interface VeluConfig {
25
+ $schema?: string;
26
+ theme?: string;
27
+ colors?: { primary?: string; light?: string; dark?: string };
28
+ appearance?: 'system' | 'light' | 'dark';
29
+ styling?: { codeblocks?: { theme?: string | { light: string; dark: string } } };
30
+ navigation: {
31
+ tabs: VeluTab[];
32
+ };
33
+ }
34
+
35
+ export interface TabMeta {
36
+ label: string;
37
+ icon?: string;
38
+ href?: string;
39
+ slugs: string[];
40
+ firstPage?: string;
41
+ }
42
+
43
+ // ── Load config ─────────────────────────────────────────────────────────────
44
+
45
+ let _cachedConfig: VeluConfig | null = null;
46
+
47
+ export function loadVeluConfig(): VeluConfig {
48
+ if (_cachedConfig) return _cachedConfig;
49
+ const configPath = resolve(process.cwd(), 'velu.json');
50
+ const raw = readFileSync(configPath, 'utf-8');
51
+ _cachedConfig = JSON.parse(raw);
52
+ return _cachedConfig!;
53
+ }
54
+
55
+ // ── Helpers ─────────────────────────────────────────────────────────────────
56
+
57
+ function pageBasename(page: string): string {
58
+ return page.split('/').pop()!;
59
+ }
60
+
61
+ /** Convert a group to a Starlight sidebar entry, using slug-based page paths */
62
+ function veluGroupToSidebar(group: VeluGroup, tabSlug: string): any {
63
+ const items: any[] = [];
64
+ for (const item of group.pages) {
65
+ if (typeof item === 'string') {
66
+ items.push(tabSlug + '/' + group.slug + '/' + pageBasename(item));
67
+ } else {
68
+ items.push(veluGroupToSidebar(item, tabSlug));
69
+ }
70
+ }
71
+ const result: any = { label: group.group, items };
72
+ if (group.tag) result.badge = group.tag;
73
+ if (group.expanded === false) result.collapsed = true;
74
+ return result;
75
+ }
76
+
77
+ /** Get the first page dest path for a tab */
78
+ function firstTabPage(tab: VeluTab): string | undefined {
79
+ if (tab.pages && tab.pages.length > 0) {
80
+ return tab.slug + '/' + pageBasename(tab.pages[0]);
81
+ }
82
+ if (tab.groups) {
83
+ for (const g of tab.groups) {
84
+ const first = firstGroupPage(g, tab.slug);
85
+ if (first) return first;
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ function firstGroupPage(group: VeluGroup, tabSlug: string): string | undefined {
92
+ for (const item of group.pages) {
93
+ if (typeof item === 'string') return tabSlug + '/' + group.slug + '/' + pageBasename(item);
94
+ const nested = firstGroupPage(item, tabSlug);
95
+ if (nested) return nested;
96
+ }
97
+ return undefined;
98
+ }
99
+
100
+ // ── Public API ──────────────────────────────────────────────────────────────
101
+
102
+ /** Build the full Starlight sidebar array from velu.json */
103
+ export function getSidebar(): any[] {
104
+ const config = loadVeluConfig();
105
+ const sidebar: any[] = [];
106
+
107
+ for (const tab of config.navigation.tabs) {
108
+ if (tab.href) continue;
109
+ const items: any[] = [];
110
+ if (tab.groups) for (const g of tab.groups) items.push(veluGroupToSidebar(g, tab.slug));
111
+ if (tab.pages) {
112
+ for (const p of tab.pages) items.push(tab.slug + '/' + pageBasename(p));
113
+ }
114
+ sidebar.push({ label: tab.tab, items });
115
+ }
116
+
117
+ return sidebar;
118
+ }
119
+
120
+ /** Get tab metadata for the header navigation */
121
+ export function getTabs(): TabMeta[] {
122
+ const config = loadVeluConfig();
123
+ const tabs: TabMeta[] = [];
124
+
125
+ for (const tab of config.navigation.tabs) {
126
+ if (tab.href) {
127
+ tabs.push({ label: tab.tab, icon: tab.icon, href: tab.href, slugs: [] });
128
+ } else {
129
+ tabs.push({
130
+ label: tab.tab,
131
+ icon: tab.icon,
132
+ slugs: [tab.slug],
133
+ firstPage: firstTabPage(tab),
134
+ });
135
+ }
136
+ }
137
+
138
+ return tabs;
139
+ }
140
+
141
+ /** Get the mapping of slug → sidebar group labels for filtering.
142
+ * Maps every slug (tab, group, nested group) to the labels that should be visible. */
143
+ export function getTabSidebarMap(): Record<string, string[]> {
144
+ const config = loadVeluConfig();
145
+ const map: Record<string, string[]> = {};
146
+
147
+ for (const tab of config.navigation.tabs) {
148
+ if (tab.href) continue;
149
+ map[tab.slug] = [tab.tab];
150
+ }
151
+
152
+ return map;
153
+ }
@@ -0,0 +1,351 @@
1
+ /* ── Velu AI Assistant ─────────────────────────────────────────────────── */
2
+
3
+ /* Fixed bottom ask bar */
4
+ .velu-ask-bar {
5
+ position: fixed;
6
+ bottom: 1.5rem;
7
+ left: 50%;
8
+ transform: translateX(-50%);
9
+ z-index: 200;
10
+ width: 100%;
11
+ max-width: 36rem;
12
+ padding: 0 1rem;
13
+ transition: opacity 0.2s, transform 0.2s;
14
+ }
15
+
16
+ .velu-ask-bar-hidden {
17
+ opacity: 0;
18
+ pointer-events: none;
19
+ transform: translateX(-50%) translateY(1rem);
20
+ }
21
+
22
+ .velu-ask-bar-inner {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 0.5rem;
26
+ padding: 0.5rem 0.75rem;
27
+ background: var(--sl-color-bg-nav);
28
+ border: 1px solid var(--sl-color-gray-5);
29
+ border-radius: 0.75rem;
30
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
31
+ }
32
+
33
+ .velu-ask-icon {
34
+ flex-shrink: 0;
35
+ color: var(--sl-color-gray-3);
36
+ }
37
+
38
+ .velu-ask-input {
39
+ flex: 1;
40
+ background: none;
41
+ border: none;
42
+ outline: none;
43
+ font: inherit;
44
+ font-size: var(--sl-text-sm);
45
+ color: var(--sl-color-white);
46
+ }
47
+
48
+ .velu-ask-input::placeholder {
49
+ color: var(--sl-color-gray-3);
50
+ }
51
+
52
+
53
+ .velu-ask-submit {
54
+ flex-shrink: 0;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ width: 28px;
59
+ height: 28px;
60
+ background: var(--sl-color-accent);
61
+ color: var(--sl-color-accent-high);
62
+ border: none;
63
+ border-radius: 50%;
64
+ cursor: pointer;
65
+ transition: opacity 0.15s;
66
+ }
67
+
68
+ .velu-ask-submit:hover { opacity: 0.85; }
69
+
70
+ /* Right-side assistant panel */
71
+ .velu-assistant-panel {
72
+ position: fixed;
73
+ top: var(--sl-nav-height, 3.5rem);
74
+ right: 0;
75
+ bottom: 0;
76
+ width: 22rem;
77
+ z-index: 50;
78
+ pointer-events: auto;
79
+ display: flex;
80
+ flex-direction: column;
81
+ background: var(--sl-color-bg);
82
+ border-left: 1px solid var(--sl-color-gray-5);
83
+ box-shadow: -4px 0 24px rgba(0, 0, 0, 0.2);
84
+ transition: width 0.2s;
85
+ }
86
+
87
+ .velu-panel-closed { display: none !important; }
88
+
89
+ .velu-assistant-expanded { width: 40rem; }
90
+
91
+ .velu-assistant-header {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ padding: 0.75rem 1rem;
96
+ border-bottom: 1px solid var(--sl-color-gray-5);
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .velu-assistant-title {
101
+ font-weight: 600;
102
+ font-size: var(--sl-text-base);
103
+ color: var(--sl-color-white);
104
+ }
105
+
106
+ .velu-assistant-actions {
107
+ display: flex;
108
+ gap: 0.25rem;
109
+ }
110
+
111
+ .velu-assistant-action {
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ width: 28px;
116
+ height: 28px;
117
+ background: none;
118
+ border: none;
119
+ border-radius: 0.25rem;
120
+ color: var(--sl-color-gray-3);
121
+ cursor: pointer;
122
+ pointer-events: auto;
123
+ transition: color 0.15s, background-color 0.15s;
124
+ }
125
+
126
+ .velu-assistant-action:hover {
127
+ color: var(--sl-color-white);
128
+ background-color: var(--sl-color-gray-6);
129
+ }
130
+
131
+ /* Messages area */
132
+ .velu-assistant-messages {
133
+ flex: 1;
134
+ overflow-y: auto;
135
+ padding: 1rem;
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: 0.75rem;
139
+ }
140
+
141
+ .velu-msg {
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: 0.35rem;
145
+ }
146
+
147
+ .velu-msg-user { align-items: flex-end; }
148
+ .velu-msg-assistant { align-items: flex-start; }
149
+
150
+ .velu-msg-bubble {
151
+ max-width: 85%;
152
+ padding: 0.6rem 0.85rem;
153
+ border-radius: 0.75rem;
154
+ font-size: var(--sl-text-sm);
155
+ line-height: 1.55;
156
+ word-break: break-word;
157
+ }
158
+
159
+ .velu-msg-bubble code {
160
+ background: var(--sl-color-gray-6);
161
+ padding: 0.1rem 0.3rem;
162
+ border-radius: 0.2rem;
163
+ font-size: 0.85em;
164
+ }
165
+
166
+ .velu-msg-bubble-user {
167
+ background: var(--sl-color-accent);
168
+ color: var(--sl-color-accent-high);
169
+ border-bottom-right-radius: 0.2rem;
170
+ }
171
+
172
+ .velu-msg-bubble-assistant {
173
+ background: var(--sl-color-gray-6);
174
+ color: var(--sl-color-white);
175
+ border-bottom-left-radius: 0.2rem;
176
+ }
177
+
178
+ /* Citations */
179
+ .velu-msg-citations {
180
+ display: flex;
181
+ flex-wrap: wrap;
182
+ gap: 0.35rem;
183
+ padding-left: 0.25rem;
184
+ }
185
+
186
+ .velu-citation-link {
187
+ font-size: var(--sl-text-xs);
188
+ color: var(--sl-color-accent);
189
+ text-decoration: none;
190
+ padding: 0.15rem 0.4rem;
191
+ background: var(--sl-color-gray-6);
192
+ border-radius: 0.25rem;
193
+ transition: background-color 0.15s;
194
+ }
195
+
196
+ .velu-citation-link:hover {
197
+ background: var(--sl-color-gray-5);
198
+ }
199
+
200
+ .velu-citation-ref {
201
+ color: var(--sl-color-accent);
202
+ text-decoration: none;
203
+ font-weight: 600;
204
+ font-size: 0.8em;
205
+ vertical-align: super;
206
+ }
207
+
208
+ /* Message actions */
209
+ .velu-msg-actions {
210
+ display: flex;
211
+ gap: 0.15rem;
212
+ padding-left: 0.25rem;
213
+ }
214
+
215
+ .velu-msg-action {
216
+ display: flex;
217
+ align-items: center;
218
+ justify-content: center;
219
+ width: 24px;
220
+ height: 24px;
221
+ background: none;
222
+ border: none;
223
+ border-radius: 0.25rem;
224
+ color: var(--sl-color-gray-4);
225
+ cursor: pointer;
226
+ transition: color 0.15s, background-color 0.15s;
227
+ }
228
+
229
+ .velu-msg-action:hover {
230
+ color: var(--sl-color-white);
231
+ background-color: var(--sl-color-gray-6);
232
+ }
233
+
234
+ /* Thinking dots */
235
+ .velu-thinking-dots {
236
+ display: inline-flex;
237
+ gap: 0.3rem;
238
+ padding: 0.2rem 0;
239
+ }
240
+
241
+ .velu-thinking-dots span {
242
+ width: 6px;
243
+ height: 6px;
244
+ border-radius: 50%;
245
+ background: var(--sl-color-gray-3);
246
+ animation: veluDotPulse 1.2s infinite;
247
+ }
248
+
249
+ .velu-thinking-dots span:nth-child(2) { animation-delay: 0.2s; }
250
+ .velu-thinking-dots span:nth-child(3) { animation-delay: 0.4s; }
251
+
252
+ @keyframes veluDotPulse {
253
+ 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
254
+ 40% { opacity: 1; transform: scale(1); }
255
+ }
256
+
257
+ /* Chat input area */
258
+ .velu-assistant-input-area {
259
+ display: flex;
260
+ align-items: center;
261
+ gap: 0.5rem;
262
+ padding: 0.75rem 1rem;
263
+ border-top: 1px solid var(--sl-color-gray-5);
264
+ flex-shrink: 0;
265
+ }
266
+
267
+ .velu-assistant-chat-input {
268
+ flex: 1;
269
+ background: var(--sl-color-gray-6);
270
+ border: 1px solid var(--sl-color-gray-5);
271
+ border-radius: 0.5rem;
272
+ padding: 0.5rem 0.75rem;
273
+ font: inherit;
274
+ font-size: var(--sl-text-sm);
275
+ color: var(--sl-color-white);
276
+ outline: none;
277
+ transition: border-color 0.15s;
278
+ }
279
+
280
+ .velu-assistant-chat-input:focus {
281
+ border-color: var(--sl-color-accent);
282
+ }
283
+
284
+ .velu-assistant-chat-input::placeholder {
285
+ color: var(--sl-color-gray-3);
286
+ }
287
+
288
+ .velu-assistant-send {
289
+ flex-shrink: 0;
290
+ display: flex;
291
+ align-items: center;
292
+ justify-content: center;
293
+ width: 32px;
294
+ height: 32px;
295
+ background: var(--sl-color-accent);
296
+ color: var(--sl-color-accent-high);
297
+ border: none;
298
+ border-radius: 50%;
299
+ cursor: pointer;
300
+ transition: opacity 0.15s;
301
+ }
302
+
303
+ .velu-assistant-send:hover { opacity: 0.85; }
304
+
305
+ /* Squeeze page layout when panel is open */
306
+ html.velu-assistant-open body {
307
+ margin-right: 22rem;
308
+ transition: margin-right 0.25s ease;
309
+ }
310
+
311
+ html.velu-assistant-wide body {
312
+ margin-right: 40rem;
313
+ }
314
+
315
+ html.velu-assistant-open .header {
316
+ padding-right: 22rem;
317
+ transition: padding-right 0.25s ease;
318
+ }
319
+
320
+ html.velu-assistant-wide .header {
321
+ padding-right: 40rem;
322
+ }
323
+
324
+ html.velu-assistant-open .velu-ask-bar {
325
+ right: 22rem;
326
+ left: auto;
327
+ transform: none;
328
+ }
329
+
330
+ html.velu-assistant-wide .velu-ask-bar {
331
+ right: 40rem;
332
+ }
333
+
334
+ /* Responsive */
335
+ @media (max-width: 50rem) {
336
+ .velu-assistant-panel {
337
+ width: 100%;
338
+ }
339
+ .velu-assistant-expanded {
340
+ width: 100%;
341
+ }
342
+ .velu-ask-bar {
343
+ max-width: calc(100% - 2rem);
344
+ }
345
+ html.velu-assistant-open body {
346
+ margin-right: 0;
347
+ }
348
+ html.velu-assistant-wide body {
349
+ margin-right: 0;
350
+ }
351
+ }
@@ -0,0 +1,183 @@
1
+ /* ── Velu sidebar tabs ─────────────────────────────────────────────────── */
2
+
3
+ :root {
4
+ --sl-sidebar-width: 16rem;
5
+ }
6
+
7
+ .velu-sidebar-tabs {
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: 0.15rem;
11
+ padding: 0.35rem;
12
+ margin-bottom: 1rem;
13
+ background-color: var(--sl-color-bg);
14
+ border: 1px solid var(--sl-color-gray-5);
15
+ border-radius: 0.5rem;
16
+ }
17
+
18
+ .velu-sidebar-tab {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: 0.5rem;
22
+ padding: 0.45rem 0.65rem;
23
+ font-size: var(--sl-text-sm);
24
+ font-weight: 600;
25
+ color: var(--sl-color-gray-3);
26
+ text-decoration: none;
27
+ border-radius: 0.375rem;
28
+ transition: color 0.15s, background-color 0.15s;
29
+ }
30
+
31
+ .velu-sidebar-tab:hover {
32
+ color: var(--sl-color-white);
33
+ background-color: var(--sl-color-gray-6);
34
+ }
35
+
36
+ .velu-sidebar-tab.active {
37
+ color: var(--sl-color-white);
38
+ background-color: var(--sl-color-gray-6);
39
+ }
40
+
41
+ :root[data-theme='light'] .velu-sidebar-tab.active {
42
+ color: var(--sl-color-white);
43
+ background-color: var(--sl-color-gray-7);
44
+ }
45
+
46
+ .velu-external-icon {
47
+ opacity: 0.4;
48
+ flex-shrink: 0;
49
+ margin-inline-start: auto;
50
+ }
51
+
52
+ /* ── Copy page button ─────────────────────────────────────────────────── */
53
+
54
+ .velu-title-row {
55
+ display: flex;
56
+ align-items: flex-start;
57
+ justify-content: space-between;
58
+ gap: 1rem;
59
+ }
60
+
61
+ .velu-title-row h1 {
62
+ margin: 0;
63
+ }
64
+
65
+ .velu-copy-page-container {
66
+ position: relative;
67
+ flex-shrink: 0;
68
+ margin-top: 0.35rem;
69
+ }
70
+
71
+ .velu-copy-split-btn {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ border: 1px solid var(--sl-color-gray-5);
75
+ border-radius: 999px;
76
+ background: var(--sl-color-bg-nav);
77
+ }
78
+
79
+ .velu-copy-main-btn {
80
+ display: inline-flex;
81
+ align-items: center;
82
+ gap: 0.4rem;
83
+ padding: 0.35rem 0.5rem 0.35rem 0.75rem;
84
+ font-size: var(--sl-text-xs);
85
+ font-weight: 500;
86
+ color: var(--sl-color-gray-3);
87
+ background: none;
88
+ border: none;
89
+ cursor: pointer;
90
+ transition: color 0.15s;
91
+ }
92
+
93
+ .velu-copy-main-btn:hover {
94
+ color: var(--sl-color-white);
95
+ }
96
+
97
+ .velu-copy-sep {
98
+ width: 1px;
99
+ height: 14px;
100
+ background-color: var(--sl-color-gray-5);
101
+ flex-shrink: 0;
102
+ }
103
+
104
+ .velu-copy-caret-btn {
105
+ display: inline-flex;
106
+ align-items: center;
107
+ padding: 0.35rem 0.5rem;
108
+ background: none;
109
+ border: none;
110
+ color: var(--sl-color-gray-3);
111
+ cursor: pointer;
112
+ transition: color 0.15s;
113
+ }
114
+
115
+ .velu-copy-caret-btn:hover {
116
+ color: var(--sl-color-white);
117
+ }
118
+
119
+ .velu-copy-chevron {
120
+ transition: transform 0.15s;
121
+ }
122
+
123
+ .velu-copy-caret-btn[aria-expanded='true'] .velu-copy-chevron {
124
+ transform: rotate(180deg);
125
+ }
126
+
127
+ .velu-copy-dropdown {
128
+ position: absolute;
129
+ right: 0;
130
+ top: calc(100% + 0.35rem);
131
+ z-index: 100;
132
+ min-width: 16rem;
133
+ padding: 0.35rem;
134
+ background: var(--sl-color-bg-nav);
135
+ border: 1px solid var(--sl-color-gray-5);
136
+ border-radius: 0.5rem;
137
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
138
+ }
139
+
140
+ .velu-copy-option {
141
+ display: flex;
142
+ align-items: flex-start;
143
+ gap: 0.5rem;
144
+ width: 100%;
145
+ padding: 0.5rem 0.6rem;
146
+ font: inherit;
147
+ font-size: var(--sl-text-sm);
148
+ color: var(--sl-color-gray-2);
149
+ text-align: left;
150
+ text-decoration: none;
151
+ background: none;
152
+ border: none;
153
+ border-radius: 0.35rem;
154
+ cursor: pointer;
155
+ transition: background-color 0.15s;
156
+ }
157
+
158
+ .velu-copy-option:hover {
159
+ background-color: var(--sl-color-gray-6);
160
+ }
161
+
162
+ .velu-copy-option svg {
163
+ flex-shrink: 0;
164
+ opacity: 0.7;
165
+ margin-top: 0.15rem;
166
+ overflow: visible;
167
+ }
168
+
169
+ .velu-copy-option-title {
170
+ font-weight: 500;
171
+ line-height: 1.3;
172
+ }
173
+
174
+ .velu-copy-option-desc {
175
+ font-size: var(--sl-text-xs);
176
+ color: var(--sl-color-gray-3);
177
+ line-height: 1.3;
178
+ }
179
+
180
+ .velu-external-arrow {
181
+ font-size: 0.75em;
182
+ opacity: 0.5;
183
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "target": "ESNext",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "allowJs": true,
7
+ "skipLibCheck": true,
8
+ "strict": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "noEmit": true,
11
+ "esModuleInterop": true,
12
+ "module": "esnext",
13
+ "moduleResolution": "bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "jsx": "react-jsx",
17
+ "incremental": true,
18
+ "paths": {
19
+ "@/*": ["./*"],
20
+ "fumadocs-mdx:collections/*": [".source/*"]
21
+ },
22
+ "plugins": [{ "name": "next" }]
23
+ },
24
+ "include": [
25
+ "next-env.d.ts",
26
+ "**/*.ts",
27
+ "**/*.tsx",
28
+ ".next/types/**/*.ts",
29
+ ".next/dev/types/**/*.ts"
30
+ ],
31
+ "exclude": ["node_modules", "src"]
32
+ }