@gmickel/gno 0.3.4 → 0.4.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.
Files changed (57) hide show
  1. package/README.md +194 -53
  2. package/assets/badges/license.svg +12 -0
  3. package/assets/badges/npm.svg +13 -0
  4. package/assets/badges/twitter.svg +22 -0
  5. package/assets/badges/website.svg +22 -0
  6. package/package.json +30 -1
  7. package/src/cli/commands/ask.ts +11 -186
  8. package/src/cli/commands/models/pull.ts +9 -4
  9. package/src/cli/commands/serve.ts +19 -0
  10. package/src/cli/program.ts +28 -0
  11. package/src/llm/registry.ts +3 -1
  12. package/src/pipeline/answer.ts +191 -0
  13. package/src/serve/CLAUDE.md +91 -0
  14. package/src/serve/bunfig.toml +2 -0
  15. package/src/serve/context.ts +181 -0
  16. package/src/serve/index.ts +7 -0
  17. package/src/serve/public/app.tsx +56 -0
  18. package/src/serve/public/components/ai-elements/code-block.tsx +176 -0
  19. package/src/serve/public/components/ai-elements/conversation.tsx +98 -0
  20. package/src/serve/public/components/ai-elements/inline-citation.tsx +285 -0
  21. package/src/serve/public/components/ai-elements/loader.tsx +96 -0
  22. package/src/serve/public/components/ai-elements/message.tsx +443 -0
  23. package/src/serve/public/components/ai-elements/prompt-input.tsx +1421 -0
  24. package/src/serve/public/components/ai-elements/sources.tsx +75 -0
  25. package/src/serve/public/components/ai-elements/suggestion.tsx +51 -0
  26. package/src/serve/public/components/preset-selector.tsx +403 -0
  27. package/src/serve/public/components/ui/badge.tsx +46 -0
  28. package/src/serve/public/components/ui/button-group.tsx +82 -0
  29. package/src/serve/public/components/ui/button.tsx +62 -0
  30. package/src/serve/public/components/ui/card.tsx +92 -0
  31. package/src/serve/public/components/ui/carousel.tsx +244 -0
  32. package/src/serve/public/components/ui/collapsible.tsx +31 -0
  33. package/src/serve/public/components/ui/command.tsx +181 -0
  34. package/src/serve/public/components/ui/dialog.tsx +141 -0
  35. package/src/serve/public/components/ui/dropdown-menu.tsx +255 -0
  36. package/src/serve/public/components/ui/hover-card.tsx +42 -0
  37. package/src/serve/public/components/ui/input-group.tsx +167 -0
  38. package/src/serve/public/components/ui/input.tsx +21 -0
  39. package/src/serve/public/components/ui/progress.tsx +28 -0
  40. package/src/serve/public/components/ui/scroll-area.tsx +56 -0
  41. package/src/serve/public/components/ui/select.tsx +188 -0
  42. package/src/serve/public/components/ui/separator.tsx +26 -0
  43. package/src/serve/public/components/ui/table.tsx +114 -0
  44. package/src/serve/public/components/ui/textarea.tsx +18 -0
  45. package/src/serve/public/components/ui/tooltip.tsx +59 -0
  46. package/src/serve/public/globals.css +226 -0
  47. package/src/serve/public/hooks/use-api.ts +112 -0
  48. package/src/serve/public/index.html +13 -0
  49. package/src/serve/public/pages/Ask.tsx +442 -0
  50. package/src/serve/public/pages/Browse.tsx +270 -0
  51. package/src/serve/public/pages/Dashboard.tsx +202 -0
  52. package/src/serve/public/pages/DocView.tsx +302 -0
  53. package/src/serve/public/pages/Search.tsx +335 -0
  54. package/src/serve/routes/api.ts +763 -0
  55. package/src/serve/server.ts +249 -0
  56. package/src/store/sqlite/adapter.ts +47 -0
  57. package/src/store/types.ts +10 -0
@@ -0,0 +1,443 @@
1
+ import type { FileUIPart, UIMessage } from 'ai';
2
+ import {
3
+ ChevronLeftIcon,
4
+ ChevronRightIcon,
5
+ PaperclipIcon,
6
+ XIcon,
7
+ } from 'lucide-react';
8
+ import type { ComponentProps, HTMLAttributes, ReactElement } from 'react';
9
+ import { createContext, memo, useContext, useEffect, useState } from 'react';
10
+ import { Streamdown } from 'streamdown';
11
+ import { cn } from '../../lib/utils';
12
+ import { Button } from '../ui/button';
13
+ import { ButtonGroup, ButtonGroupText } from '../ui/button-group';
14
+ import {
15
+ Tooltip,
16
+ TooltipContent,
17
+ TooltipProvider,
18
+ TooltipTrigger,
19
+ } from '../ui/tooltip';
20
+
21
+ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
22
+ from: UIMessage['role'];
23
+ };
24
+
25
+ export const Message = ({ className, from, ...props }: MessageProps) => (
26
+ <div
27
+ className={cn(
28
+ 'group flex w-full max-w-[95%] flex-col gap-2',
29
+ from === 'user' ? 'is-user ml-auto justify-end' : 'is-assistant',
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+
36
+ export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
37
+
38
+ export const MessageContent = ({
39
+ children,
40
+ className,
41
+ ...props
42
+ }: MessageContentProps) => (
43
+ <div
44
+ className={cn(
45
+ 'is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm',
46
+ 'group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground',
47
+ 'group-[.is-assistant]:text-foreground',
48
+ className
49
+ )}
50
+ {...props}
51
+ >
52
+ {children}
53
+ </div>
54
+ );
55
+
56
+ export type MessageActionsProps = ComponentProps<'div'>;
57
+
58
+ export const MessageActions = ({
59
+ className,
60
+ children,
61
+ ...props
62
+ }: MessageActionsProps) => (
63
+ <div className={cn('flex items-center gap-1', className)} {...props}>
64
+ {children}
65
+ </div>
66
+ );
67
+
68
+ export type MessageActionProps = ComponentProps<typeof Button> & {
69
+ tooltip?: string;
70
+ label?: string;
71
+ };
72
+
73
+ export const MessageAction = ({
74
+ tooltip,
75
+ children,
76
+ label,
77
+ variant = 'ghost',
78
+ size = 'icon-sm',
79
+ ...props
80
+ }: MessageActionProps) => {
81
+ const button = (
82
+ <Button size={size} type="button" variant={variant} {...props}>
83
+ {children}
84
+ <span className="sr-only">{label || tooltip}</span>
85
+ </Button>
86
+ );
87
+
88
+ if (tooltip) {
89
+ return (
90
+ <TooltipProvider>
91
+ <Tooltip>
92
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
93
+ <TooltipContent>
94
+ <p>{tooltip}</p>
95
+ </TooltipContent>
96
+ </Tooltip>
97
+ </TooltipProvider>
98
+ );
99
+ }
100
+
101
+ return button;
102
+ };
103
+
104
+ interface MessageBranchContextType {
105
+ currentBranch: number;
106
+ totalBranches: number;
107
+ goToPrevious: () => void;
108
+ goToNext: () => void;
109
+ branches: ReactElement[];
110
+ setBranches: (branches: ReactElement[]) => void;
111
+ }
112
+
113
+ const MessageBranchContext = createContext<MessageBranchContextType | null>(
114
+ null
115
+ );
116
+
117
+ const useMessageBranch = () => {
118
+ const context = useContext(MessageBranchContext);
119
+
120
+ if (!context) {
121
+ throw new Error(
122
+ 'MessageBranch components must be used within MessageBranch'
123
+ );
124
+ }
125
+
126
+ return context;
127
+ };
128
+
129
+ export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
130
+ defaultBranch?: number;
131
+ onBranchChange?: (branchIndex: number) => void;
132
+ };
133
+
134
+ export const MessageBranch = ({
135
+ defaultBranch = 0,
136
+ onBranchChange,
137
+ className,
138
+ ...props
139
+ }: MessageBranchProps) => {
140
+ const [currentBranch, setCurrentBranch] = useState(defaultBranch);
141
+ const [branches, setBranches] = useState<ReactElement[]>([]);
142
+
143
+ const handleBranchChange = (newBranch: number) => {
144
+ setCurrentBranch(newBranch);
145
+ onBranchChange?.(newBranch);
146
+ };
147
+
148
+ const goToPrevious = () => {
149
+ const newBranch =
150
+ currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
151
+ handleBranchChange(newBranch);
152
+ };
153
+
154
+ const goToNext = () => {
155
+ const newBranch =
156
+ currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
157
+ handleBranchChange(newBranch);
158
+ };
159
+
160
+ const contextValue: MessageBranchContextType = {
161
+ currentBranch,
162
+ totalBranches: branches.length,
163
+ goToPrevious,
164
+ goToNext,
165
+ branches,
166
+ setBranches,
167
+ };
168
+
169
+ return (
170
+ <MessageBranchContext.Provider value={contextValue}>
171
+ <div
172
+ className={cn('grid w-full gap-2 [&>div]:pb-0', className)}
173
+ {...props}
174
+ />
175
+ </MessageBranchContext.Provider>
176
+ );
177
+ };
178
+
179
+ export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
180
+
181
+ export const MessageBranchContent = ({
182
+ children,
183
+ ...props
184
+ }: MessageBranchContentProps) => {
185
+ const { currentBranch, setBranches, branches } = useMessageBranch();
186
+ const childrenArray = Array.isArray(children) ? children : [children];
187
+
188
+ // Use useEffect to update branches when they change
189
+ useEffect(() => {
190
+ if (branches.length !== childrenArray.length) {
191
+ setBranches(childrenArray);
192
+ }
193
+ }, [childrenArray, branches, setBranches]);
194
+
195
+ return childrenArray.map((branch, index) => (
196
+ <div
197
+ className={cn(
198
+ 'grid gap-2 overflow-hidden [&>div]:pb-0',
199
+ index === currentBranch ? 'block' : 'hidden'
200
+ )}
201
+ key={branch.key}
202
+ {...props}
203
+ >
204
+ {branch}
205
+ </div>
206
+ ));
207
+ };
208
+
209
+ export type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
210
+ from: UIMessage['role'];
211
+ };
212
+
213
+ export const MessageBranchSelector = ({
214
+ className,
215
+ from,
216
+ ...props
217
+ }: MessageBranchSelectorProps) => {
218
+ const { totalBranches } = useMessageBranch();
219
+
220
+ // Don't render if there's only one branch
221
+ if (totalBranches <= 1) {
222
+ return null;
223
+ }
224
+
225
+ return (
226
+ <ButtonGroup
227
+ className="[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md"
228
+ orientation="horizontal"
229
+ {...props}
230
+ />
231
+ );
232
+ };
233
+
234
+ export type MessageBranchPreviousProps = ComponentProps<typeof Button>;
235
+
236
+ export const MessageBranchPrevious = ({
237
+ children,
238
+ ...props
239
+ }: MessageBranchPreviousProps) => {
240
+ const { goToPrevious, totalBranches } = useMessageBranch();
241
+
242
+ return (
243
+ <Button
244
+ aria-label="Previous branch"
245
+ disabled={totalBranches <= 1}
246
+ onClick={goToPrevious}
247
+ size="icon-sm"
248
+ type="button"
249
+ variant="ghost"
250
+ {...props}
251
+ >
252
+ {children ?? <ChevronLeftIcon size={14} />}
253
+ </Button>
254
+ );
255
+ };
256
+
257
+ export type MessageBranchNextProps = ComponentProps<typeof Button>;
258
+
259
+ export const MessageBranchNext = ({
260
+ children,
261
+ className,
262
+ ...props
263
+ }: MessageBranchNextProps) => {
264
+ const { goToNext, totalBranches } = useMessageBranch();
265
+
266
+ return (
267
+ <Button
268
+ aria-label="Next branch"
269
+ disabled={totalBranches <= 1}
270
+ onClick={goToNext}
271
+ size="icon-sm"
272
+ type="button"
273
+ variant="ghost"
274
+ {...props}
275
+ >
276
+ {children ?? <ChevronRightIcon size={14} />}
277
+ </Button>
278
+ );
279
+ };
280
+
281
+ export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
282
+
283
+ export const MessageBranchPage = ({
284
+ className,
285
+ ...props
286
+ }: MessageBranchPageProps) => {
287
+ const { currentBranch, totalBranches } = useMessageBranch();
288
+
289
+ return (
290
+ <ButtonGroupText
291
+ className={cn(
292
+ 'border-none bg-transparent text-muted-foreground shadow-none',
293
+ className
294
+ )}
295
+ {...props}
296
+ >
297
+ {currentBranch + 1} of {totalBranches}
298
+ </ButtonGroupText>
299
+ );
300
+ };
301
+
302
+ export type MessageResponseProps = ComponentProps<typeof Streamdown>;
303
+
304
+ export const MessageResponse = memo(
305
+ ({ className, ...props }: MessageResponseProps) => (
306
+ <Streamdown
307
+ className={cn(
308
+ 'size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',
309
+ className
310
+ )}
311
+ {...props}
312
+ />
313
+ ),
314
+ (prevProps, nextProps) => prevProps.children === nextProps.children
315
+ );
316
+
317
+ MessageResponse.displayName = 'MessageResponse';
318
+
319
+ export type MessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {
320
+ data: FileUIPart;
321
+ className?: string;
322
+ onRemove?: () => void;
323
+ };
324
+
325
+ export function MessageAttachment({
326
+ data,
327
+ className,
328
+ onRemove,
329
+ ...props
330
+ }: MessageAttachmentProps) {
331
+ const filename = data.filename || '';
332
+ const mediaType =
333
+ data.mediaType?.startsWith('image/') && data.url ? 'image' : 'file';
334
+ const isImage = mediaType === 'image';
335
+ const attachmentLabel = filename || (isImage ? 'Image' : 'Attachment');
336
+
337
+ return (
338
+ <div
339
+ className={cn(
340
+ 'group relative size-24 overflow-hidden rounded-lg',
341
+ className
342
+ )}
343
+ {...props}
344
+ >
345
+ {isImage ? (
346
+ <>
347
+ <img
348
+ alt={filename || 'attachment'}
349
+ className="size-full object-cover"
350
+ height={100}
351
+ src={data.url}
352
+ width={100}
353
+ />
354
+ {onRemove && (
355
+ <Button
356
+ aria-label="Remove attachment"
357
+ className="absolute top-2 right-2 size-6 rounded-full bg-background/80 p-0 opacity-0 backdrop-blur-sm transition-opacity hover:bg-background group-hover:opacity-100 [&>svg]:size-3"
358
+ onClick={(e) => {
359
+ e.stopPropagation();
360
+ onRemove();
361
+ }}
362
+ type="button"
363
+ variant="ghost"
364
+ >
365
+ <XIcon />
366
+ <span className="sr-only">Remove</span>
367
+ </Button>
368
+ )}
369
+ </>
370
+ ) : (
371
+ <>
372
+ <Tooltip>
373
+ <TooltipTrigger asChild>
374
+ <div className="flex size-full shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
375
+ <PaperclipIcon className="size-4" />
376
+ </div>
377
+ </TooltipTrigger>
378
+ <TooltipContent>
379
+ <p>{attachmentLabel}</p>
380
+ </TooltipContent>
381
+ </Tooltip>
382
+ {onRemove && (
383
+ <Button
384
+ aria-label="Remove attachment"
385
+ className="size-6 shrink-0 rounded-full p-0 opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100 [&>svg]:size-3"
386
+ onClick={(e) => {
387
+ e.stopPropagation();
388
+ onRemove();
389
+ }}
390
+ type="button"
391
+ variant="ghost"
392
+ >
393
+ <XIcon />
394
+ <span className="sr-only">Remove</span>
395
+ </Button>
396
+ )}
397
+ </>
398
+ )}
399
+ </div>
400
+ );
401
+ }
402
+
403
+ export type MessageAttachmentsProps = ComponentProps<'div'>;
404
+
405
+ export function MessageAttachments({
406
+ children,
407
+ className,
408
+ ...props
409
+ }: MessageAttachmentsProps) {
410
+ if (!children) {
411
+ return null;
412
+ }
413
+
414
+ return (
415
+ <div
416
+ className={cn(
417
+ 'ml-auto flex w-fit flex-wrap items-start gap-2',
418
+ className
419
+ )}
420
+ {...props}
421
+ >
422
+ {children}
423
+ </div>
424
+ );
425
+ }
426
+
427
+ export type MessageToolbarProps = ComponentProps<'div'>;
428
+
429
+ export const MessageToolbar = ({
430
+ className,
431
+ children,
432
+ ...props
433
+ }: MessageToolbarProps) => (
434
+ <div
435
+ className={cn(
436
+ 'mt-4 flex w-full items-center justify-between gap-4',
437
+ className
438
+ )}
439
+ {...props}
440
+ >
441
+ {children}
442
+ </div>
443
+ );