@neo4j-ndl/react 4.16.7 → 4.16.9

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 (109) hide show
  1. package/lib/cjs/color-picker/ColorPicker.js +3 -1
  2. package/lib/cjs/color-picker/ColorPicker.js.map +1 -1
  3. package/lib/cjs/color-picker/stories/color-picker-dialog.story.js +40 -0
  4. package/lib/cjs/color-picker/stories/color-picker-dialog.story.js.map +1 -0
  5. package/lib/cjs/color-picker/stories/color-picker-popover.story.js +32 -0
  6. package/lib/cjs/color-picker/stories/color-picker-popover.story.js.map +1 -0
  7. package/lib/cjs/color-picker/stories/index.js +9 -1
  8. package/lib/cjs/color-picker/stories/index.js.map +1 -1
  9. package/lib/cjs/popover/use-popover.js +1 -1
  10. package/lib/cjs/popover/use-popover.js.map +1 -1
  11. package/lib/cjs/select/Select.js +2 -0
  12. package/lib/cjs/select/Select.js.map +1 -1
  13. package/lib/esm/color-picker/ColorPicker.js +3 -1
  14. package/lib/esm/color-picker/ColorPicker.js.map +1 -1
  15. package/lib/esm/color-picker/stories/color-picker-dialog.story.js +38 -0
  16. package/lib/esm/color-picker/stories/color-picker-dialog.story.js.map +1 -0
  17. package/lib/esm/color-picker/stories/color-picker-popover.story.js +30 -0
  18. package/lib/esm/color-picker/stories/color-picker-popover.story.js.map +1 -0
  19. package/lib/esm/color-picker/stories/index.js +6 -0
  20. package/lib/esm/color-picker/stories/index.js.map +1 -1
  21. package/lib/esm/popover/use-popover.js +1 -1
  22. package/lib/esm/popover/use-popover.js.map +1 -1
  23. package/lib/esm/select/Select.js +2 -0
  24. package/lib/esm/select/Select.js.map +1 -1
  25. package/lib/types/color-picker/ColorPicker.d.ts +4 -0
  26. package/lib/types/color-picker/ColorPicker.d.ts.map +1 -1
  27. package/lib/types/color-picker/stories/color-picker-dialog.story.d.ts +24 -0
  28. package/lib/types/color-picker/stories/color-picker-dialog.story.d.ts.map +1 -0
  29. package/lib/types/color-picker/stories/color-picker-popover.story.d.ts +24 -0
  30. package/lib/types/color-picker/stories/color-picker-popover.story.d.ts.map +1 -0
  31. package/lib/types/color-picker/stories/index.d.ts +4 -0
  32. package/lib/types/color-picker/stories/index.d.ts.map +1 -1
  33. package/lib/types/select/Select.d.ts.map +1 -1
  34. package/package.json +5 -2
  35. package/skills/ndl-react/SKILL.md +152 -0
  36. package/skills/ndl-react/components/accordion.md +386 -0
  37. package/skills/ndl-react/components/ai/code-preview.md +128 -0
  38. package/skills/ndl-react/components/ai/file-tag.md +90 -0
  39. package/skills/ndl-react/components/ai/image-tag.md +91 -0
  40. package/skills/ndl-react/components/ai/more-files.md +73 -0
  41. package/skills/ndl-react/components/ai/presence.md +54 -0
  42. package/skills/ndl-react/components/ai/preview.md +498 -0
  43. package/skills/ndl-react/components/ai/prompt.md +362 -0
  44. package/skills/ndl-react/components/ai/reasoning.md +95 -0
  45. package/skills/ndl-react/components/ai/response.md +1109 -0
  46. package/skills/ndl-react/components/ai/suggestion.md +84 -0
  47. package/skills/ndl-react/components/ai/thinking.md +43 -0
  48. package/skills/ndl-react/components/ai/tool-call.md +43 -0
  49. package/skills/ndl-react/components/ai/user-bubble.md +187 -0
  50. package/skills/ndl-react/components/avatar.md +169 -0
  51. package/skills/ndl-react/components/banner.md +225 -0
  52. package/skills/ndl-react/components/box.md +39 -0
  53. package/skills/ndl-react/components/breadcrumbs.md +245 -0
  54. package/skills/ndl-react/components/checkbox.md +269 -0
  55. package/skills/ndl-react/components/clean-icon-button.md +294 -0
  56. package/skills/ndl-react/components/clipboard-button.md +154 -0
  57. package/skills/ndl-react/components/code-block.md +276 -0
  58. package/skills/ndl-react/components/code.md +68 -0
  59. package/skills/ndl-react/components/color-picker.md +131 -0
  60. package/skills/ndl-react/components/conditional-wrap.md +46 -0
  61. package/skills/ndl-react/components/data-grid.md +3121 -0
  62. package/skills/ndl-react/components/date-picker.md +652 -0
  63. package/skills/ndl-react/components/dialog.md +485 -0
  64. package/skills/ndl-react/components/dismissible-tag.md +136 -0
  65. package/skills/ndl-react/components/divider.md +96 -0
  66. package/skills/ndl-react/components/drawer.md +543 -0
  67. package/skills/ndl-react/components/dropdown-button.md +161 -0
  68. package/skills/ndl-react/components/dropzone.md +371 -0
  69. package/skills/ndl-react/components/filled-button.md +26 -0
  70. package/skills/ndl-react/components/flex.md +82 -0
  71. package/skills/ndl-react/components/graph-label.md +167 -0
  72. package/skills/ndl-react/components/icon-button-array.md +187 -0
  73. package/skills/ndl-react/components/icon-button.md +193 -0
  74. package/skills/ndl-react/components/inline-edit.md +209 -0
  75. package/skills/ndl-react/components/kbd.md +63 -0
  76. package/skills/ndl-react/components/loading-bar.md +43 -0
  77. package/skills/ndl-react/components/loading-spinner.md +57 -0
  78. package/skills/ndl-react/components/logo.md +57 -0
  79. package/skills/ndl-react/components/menu.md +993 -0
  80. package/skills/ndl-react/components/modal.md +27 -0
  81. package/skills/ndl-react/components/next/tree-view.md +966 -0
  82. package/skills/ndl-react/components/outlined-button.md +26 -0
  83. package/skills/ndl-react/components/popover.md +283 -0
  84. package/skills/ndl-react/components/progress-bar.md +70 -0
  85. package/skills/ndl-react/components/radio.md +150 -0
  86. package/skills/ndl-react/components/read-only-tag.md +96 -0
  87. package/skills/ndl-react/components/segmented-control.md +200 -0
  88. package/skills/ndl-react/components/select-icon-button.md +183 -0
  89. package/skills/ndl-react/components/select.md +530 -0
  90. package/skills/ndl-react/components/selectable-tag.md +203 -0
  91. package/skills/ndl-react/components/side-navigation.md +652 -0
  92. package/skills/ndl-react/components/skeleton.md +274 -0
  93. package/skills/ndl-react/components/slider.md +308 -0
  94. package/skills/ndl-react/components/spotlight.md +796 -0
  95. package/skills/ndl-react/components/status-indicator.md +57 -0
  96. package/skills/ndl-react/components/status-label.md +132 -0
  97. package/skills/ndl-react/components/switch.md +167 -0
  98. package/skills/ndl-react/components/tabs.md +433 -0
  99. package/skills/ndl-react/components/tag.md +91 -0
  100. package/skills/ndl-react/components/text-area.md +178 -0
  101. package/skills/ndl-react/components/text-button.md +25 -0
  102. package/skills/ndl-react/components/text-input.md +475 -0
  103. package/skills/ndl-react/components/text-link.md +119 -0
  104. package/skills/ndl-react/components/time-picker.md +137 -0
  105. package/skills/ndl-react/components/timezone-picker.md +496 -0
  106. package/skills/ndl-react/components/tooltip.md +478 -0
  107. package/skills/ndl-react/components/tree-view.md +545 -0
  108. package/skills/ndl-react/components/typography.md +72 -0
  109. package/skills/ndl-react/components/wizard.md +152 -0
@@ -0,0 +1,1109 @@
1
+ # Response
2
+
3
+ Import: `import { Response } from '@neo4j-ndl/react/ai'`
4
+
5
+ ## Props
6
+
7
+ ### Response
8
+
9
+ | Prop | Type | Required | Default | Description |
10
+ |------|------|----------|---------|-------------|
11
+ | `children` | `string` | ✅ | | The response from the AI |
12
+ | `isAnimating` | `boolean` | | | Whether the response is animating |
13
+ | `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
14
+
15
+ ## Examples
16
+
17
+ ### Default
18
+
19
+ ```tsx
20
+ import { Response } from '@neo4j-ndl/react/ai';
21
+
22
+ const markdownContent = `
23
+ # Hello World
24
+
25
+ This is a **bold** text and this is an *italic* text.
26
+
27
+ Here is a list:
28
+ - Item 1
29
+ - Item 2
30
+ - Item 3
31
+
32
+ And a code block:
33
+
34
+ \`\`\`typescript
35
+ const greeting = "Hello World";
36
+ console.log(greeting);
37
+ \`\`\`
38
+
39
+ [Link to Neo4j](https://neo4j.com)
40
+ `;
41
+
42
+ export const Component = () => {
43
+ return <Response>{markdownContent}</Response>;
44
+ };
45
+
46
+ export default Component;
47
+ ```
48
+
49
+ ### All Components
50
+
51
+ ```tsx
52
+ import { Response } from '@neo4j-ndl/react/ai';
53
+
54
+ const comprehensiveMarkdown = `
55
+ # All Markdown Components Demo
56
+
57
+ This story demonstrates all the markdown components that the Response component can render.
58
+
59
+ ## Headings
60
+
61
+ # Heading 1
62
+ ## Heading 2
63
+ ### Heading 3
64
+ #### Heading 4
65
+ ##### Heading 5
66
+ ###### Heading 6
67
+
68
+ ## Text Formatting
69
+
70
+ This is **bold text** and this is *italic text*.
71
+
72
+ You can also use __bold__ and _italic_ with underscores.
73
+
74
+ Combine them: **bold and *italic* together**.
75
+
76
+ ## Lists
77
+
78
+ ### Unordered Lists
79
+
80
+ - Item 1
81
+ - Item 2
82
+ - Item 3
83
+ - Nested item 1
84
+ - Nested item 2
85
+ - Deeply nested item
86
+ - <h1>Nested nested heading</h1>
87
+
88
+ ### Ordered Lists
89
+
90
+ 1. First item
91
+ 2. Second item
92
+ 3. Third item
93
+ 1. Nested numbered item
94
+ 2. Another nested item
95
+ 3. <h2>Nested nested heading</h2>
96
+
97
+ ## Code
98
+
99
+ ### Inline Code
100
+
101
+ Use \`const x = 5\` for inline code.
102
+
103
+ ### Code Blocks
104
+
105
+ #### JavaScript
106
+
107
+ \`\`\`javascript
108
+ const greeting = "Hello World";
109
+ console.log(greeting);
110
+
111
+ function sayHello(name) {
112
+ return \`Hello, \${name}!\`;
113
+ }
114
+ \`\`\`
115
+
116
+ #### Python
117
+
118
+ \`\`\`python
119
+ def greet(name):
120
+ return f"Hello, {name}!"
121
+
122
+ print(greet("World"))
123
+ \`\`\`
124
+
125
+ #### Cypher
126
+
127
+ \`\`\`cypher
128
+ MATCH (n:Person)-[:KNOWS]->(m:Person)
129
+ WHERE n.name = "Alice"
130
+ RETURN m.name
131
+ \`\`\`
132
+
133
+ #### TypeScript
134
+
135
+ \`\`\`typescript
136
+ interface User {
137
+ name: string;
138
+ age: number;
139
+ }
140
+
141
+ const user: User = {
142
+ name: "John",
143
+ age: 30
144
+ };
145
+ \`\`\`
146
+
147
+ #### Without Language Specification
148
+
149
+ \`\`\`
150
+ Plain code block
151
+ without syntax highlighting
152
+ \`\`\`
153
+
154
+ ## Links
155
+
156
+ Visit [Neo4j](https://neo4j.com) for more information.
157
+
158
+ Multiple links: [Documentation](https://neo4j.com/docs) and [Community](https://community.neo4j.com)
159
+
160
+ ## Blockquotes
161
+
162
+ > This is a blockquote.
163
+ > It can span multiple lines.
164
+
165
+ > ### Blockquote with heading
166
+ >
167
+ > You can also include other markdown inside blockquotes.
168
+ >
169
+ > - Like lists
170
+ > - And other elements
171
+
172
+ ### Nested Blockquotes
173
+
174
+ > Outer quote
175
+ >> Nested quote
176
+ >>> Deeply nested quote
177
+
178
+ ## Horizontal Rules
179
+
180
+ Content above the rule
181
+
182
+ ---
183
+
184
+ Content below the rule
185
+
186
+ ## Images
187
+
188
+ ![A smiling man](https://media.istockphoto.com/id/1334716681/photo/a-smiling-man.jpg?s=612x612&w=0&k=20&c=U6rkSDpQMzkcJEqx2hAa63fNLIhqnZb31Xuc_QSi648=)
189
+
190
+ ## Paragraphs
191
+
192
+ This is a paragraph with multiple sentences. It demonstrates how regular text is rendered. You can write long-form content that spans multiple lines.
193
+
194
+ Another paragraph here. Paragraphs are separated by blank lines in markdown.
195
+
196
+ ## Mixed Content Example
197
+
198
+ Here's a practical example combining multiple elements:
199
+
200
+ ### Database Query Tutorial
201
+
202
+ To query your Neo4j database, follow these steps:
203
+
204
+ 1. **Connect to your database**
205
+
206
+ Use the following code:
207
+
208
+ \`\`\`javascript
209
+ const driver = neo4j.driver(
210
+ 'bolt://localhost:7687',
211
+ neo4j.auth.basic('neo4j', 'password')
212
+ );
213
+ \`\`\`
214
+
215
+ 2. **Write your Cypher query**
216
+
217
+ \`\`\`cypher
218
+ MATCH (n:Movie)
219
+ WHERE n.released > 2000
220
+ RETURN n.title, n.released
221
+ ORDER BY n.released DESC
222
+ LIMIT 10
223
+ \`\`\`
224
+
225
+ 3. **Execute and process results**
226
+
227
+ > **Note**: Always close your session after use!
228
+
229
+ For more information, visit the [Neo4j Documentation](https://neo4j.com/docs).
230
+
231
+ ---
232
+
233
+ ## Special Characters and Escaping
234
+
235
+ You can escape special characters: \\* \\_ \\[ \\]
236
+
237
+ ## Preformatted Text
238
+
239
+ Use code blocks for preformatted text with preserved spacing:
240
+
241
+ \`\`\`
242
+ This text
243
+ has preserved
244
+ indentation
245
+ \`\`\`
246
+
247
+ | Item | In Stock | Price |
248
+ | :---------------- | :------: | ----: |
249
+ | Python Hat | True | 222222223.99 |
250
+ | SQL Hat | True | 23.99 |
251
+ | Codecademy Tee | False | 19.99 |
252
+ | Codecademy Hoodie | False | 42.99 |
253
+ `;
254
+
255
+ export const Component = () => {
256
+ return (
257
+ <div className="max-w-4xl">
258
+ <Response>{comprehensiveMarkdown}</Response>
259
+ </div>
260
+ );
261
+ };
262
+
263
+ export default Component;
264
+ ```
265
+
266
+ ### Code Examples
267
+
268
+ ```tsx
269
+ import { Response } from '@neo4j-ndl/react/ai';
270
+
271
+ const codeExamplesMarkdown = `
272
+ # Code Examples
273
+
274
+ This story focuses on different code rendering capabilities.
275
+
276
+ ## Multiple Programming Languages
277
+
278
+ ### JavaScript
279
+
280
+ \`\`\`javascript
281
+ // Array methods
282
+ const numbers = [1, 2, 3, 4, 5];
283
+ const doubled = numbers.map(n => n * 2);
284
+ const sum = numbers.reduce((acc, n) => acc + n, 0);
285
+
286
+ console.log('Doubled:', doubled);
287
+ console.log('Sum:', sum);
288
+ \`\`\`
289
+
290
+ ### TypeScript
291
+
292
+ \`\`\`typescript
293
+ // Generics
294
+ function identity<T>(arg: T): T {
295
+ return arg;
296
+ }
297
+
298
+ const result = identity<string>("Hello");
299
+ const num = identity(42);
300
+ \`\`\`
301
+
302
+ ### Python
303
+
304
+ \`\`\`python
305
+ # List comprehension
306
+ squares = [x**2 for x in range(10)]
307
+ evens = [x for x in range(20) if x % 2 == 0]
308
+
309
+ # Dictionary comprehension
310
+ square_dict = {x: x**2 for x in range(5)}
311
+
312
+ print(f"Squares: {squares}")
313
+ print(f"Evens: {evens}")
314
+ \`\`\`
315
+
316
+ ### Cypher
317
+
318
+ \`\`\`cypher
319
+ // Create nodes and relationships
320
+ CREATE (alice:Person {name: 'Alice', age: 30})
321
+ CREATE (bob:Person {name: 'Bob', age: 25})
322
+ CREATE (alice)-[:KNOWS {since: 2020}]->(bob)
323
+ RETURN alice, bob
324
+ \`\`\`
325
+
326
+ ### SQL
327
+
328
+ \`\`\`sql
329
+ -- Complex query
330
+ SELECT
331
+ u.name,
332
+ COUNT(o.id) as order_count,
333
+ SUM(o.total) as total_spent
334
+ FROM users u
335
+ LEFT JOIN orders o ON u.id = o.user_id
336
+ WHERE u.active = true
337
+ GROUP BY u.id, u.name
338
+ HAVING COUNT(o.id) > 5
339
+ ORDER BY total_spent DESC
340
+ LIMIT 10;
341
+ \`\`\`
342
+
343
+ ### Rust
344
+
345
+ \`\`\`rust
346
+ // Pattern matching
347
+ fn describe_number(n: i32) -> String {
348
+ match n {
349
+ 0 => "zero".to_string(),
350
+ 1..=10 => "small".to_string(),
351
+ 11..=100 => "medium".to_string(),
352
+ _ => "large".to_string(),
353
+ }
354
+ }
355
+ \`\`\`
356
+
357
+ ### Go
358
+
359
+ \`\`\`go
360
+ // Goroutines and channels
361
+ func worker(id int, jobs <-chan int, results chan<- int) {
362
+ for j := range jobs {
363
+ fmt.Printf("worker %d processing job %d\\n", id, j)
364
+ results <- j * 2
365
+ }
366
+ }
367
+ \`\`\`
368
+
369
+ ### JSON
370
+
371
+ \`\`\`json
372
+ {
373
+ "name": "Response Component",
374
+ "version": "1.0.0",
375
+ "features": [
376
+ "markdown rendering",
377
+ "syntax highlighting",
378
+ "streaming support"
379
+ ],
380
+ "config": {
381
+ "theme": "dark",
382
+ "lineNumbers": true
383
+ }
384
+ }
385
+ \`\`\`
386
+
387
+ ### YAML
388
+
389
+ \`\`\`yaml
390
+ # Configuration file
391
+ app:
392
+ name: MyApp
393
+ version: 1.0.0
394
+ port: 3000
395
+
396
+ database:
397
+ host: localhost
398
+ port: 5432
399
+ credentials:
400
+ username: admin
401
+ password: secret
402
+
403
+ features:
404
+ - authentication
405
+ - logging
406
+ - caching
407
+ \`\`\`
408
+
409
+ ### Shell/Bash
410
+
411
+ \`\`\`bash
412
+ #!/bin/bash
413
+
414
+ # Deploy script
415
+ echo "Starting deployment..."
416
+
417
+ npm install
418
+ npm run build
419
+ npm test
420
+
421
+ if [ $? -eq 0 ]; then
422
+ echo "Deployment successful!"
423
+ else
424
+ echo "Deployment failed!"
425
+ exit 1
426
+ fi
427
+ \`\`\`
428
+
429
+ ## Inline Code
430
+
431
+ You can use inline code like \`const x = 5\`, \`npm install\`, or \`SELECT * FROM users\` within sentences.
432
+
433
+ Mixed with text: The \`Array.map()\` method creates a new array and the \`filter()\` method filters elements.
434
+
435
+ ## Code Without Language
436
+
437
+ \`\`\`
438
+ Plain text code block
439
+ No syntax highlighting
440
+ Just monospace font
441
+ \`\`\`
442
+
443
+ ## Long Code Blocks
444
+
445
+ \`\`\`javascript
446
+ // Complex React component
447
+ import React, { useState, useEffect, useCallback } from 'react';
448
+
449
+ const UserDashboard = ({ userId }) => {
450
+ const [user, setUser] = useState(null);
451
+ const [loading, setLoading] = useState(true);
452
+ const [error, setError] = useState(null);
453
+
454
+ const fetchUser = useCallback(async () => {
455
+ try {
456
+ setLoading(true);
457
+ const response = await fetch(\`/api/users/\${userId}\`);
458
+ if (!response.ok) throw new Error('Failed to fetch user');
459
+ const data = await response.json();
460
+ setUser(data);
461
+ } catch (err) {
462
+ setError(err.message);
463
+ } finally {
464
+ setLoading(false);
465
+ }
466
+ }, [userId]);
467
+
468
+ useEffect(() => {
469
+ fetchUser();
470
+ }, [fetchUser]);
471
+
472
+ if (loading) return <div>Loading...</div>;
473
+ if (error) return <div>Error: {error}</div>;
474
+ if (!user) return <div>No user found</div>;
475
+
476
+ return (
477
+ <div className="dashboard">
478
+ <h1>{user.name}</h1>
479
+ <p>Email: {user.email}</p>
480
+ <button onClick={fetchUser}>Refresh</button>
481
+ </div>
482
+ );
483
+ };
484
+
485
+ export default UserDashboard;
486
+ \`\`\`
487
+ `;
488
+
489
+ export const Component = () => {
490
+ return (
491
+ <div className="max-w-4xl">
492
+ <Response>{codeExamplesMarkdown}</Response>
493
+ </div>
494
+ );
495
+ };
496
+
497
+ export default Component;
498
+ ```
499
+
500
+ ### Full Example
501
+
502
+ ```tsx
503
+ import { CleanIconButton, TextLink, Typography } from '@neo4j-ndl/react';
504
+ import {
505
+ Prompt,
506
+ Response,
507
+ Suggestion,
508
+ Thinking,
509
+ UserBubble,
510
+ } from '@neo4j-ndl/react/ai';
511
+ import {
512
+ ArrowPathIconOutline,
513
+ Cog6ToothIconOutline,
514
+ HandThumbDownIconOutline,
515
+ PlusIconOutline,
516
+ Square2StackIconOutline,
517
+ XMarkIconOutline,
518
+ } from '@neo4j-ndl/react/icons';
519
+ import { useEffect, useRef, useState } from 'react';
520
+
521
+ const FAKE_RESPONSES = [
522
+ `Here is a simple response with some **bold text** and *italics*.`,
523
+ `Here is a list of items:
524
+ - Item 1
525
+ - Item 2
526
+ - Item 3`,
527
+ `Here is a code block example:
528
+
529
+ \`\`\`typescript
530
+ const greeting = "Hello World";
531
+ console.log(greeting);
532
+ \`\`\`
533
+ `,
534
+ `# Heading 1
535
+ ## Heading 2
536
+ ### Heading 3
537
+
538
+ Some text under headings.`,
539
+ `You can also use tables:
540
+
541
+ | Header 1 | Header 2 |
542
+ |Data 1|Data 2|
543
+ |Data 3|Data 4|
544
+ `,
545
+ ];
546
+
547
+ export const Component = () => {
548
+ const [messages, setMessages] = useState<
549
+ {
550
+ role: 'user' | 'assistant';
551
+ content: string;
552
+ thinkingTime?: number;
553
+ done?: boolean;
554
+ }[]
555
+ >([]);
556
+ const [prompt, setPrompt] = useState('');
557
+ const [isThinking, setIsThinking] = useState(false);
558
+ const [isStreaming, setIsStreaming] = useState(false);
559
+ const [responseIndex, setResponseIndex] = useState(0);
560
+ const messagesEndRef = useRef<HTMLDivElement>(null);
561
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
562
+ const intervalRef = useRef<NodeJS.Timeout | null>(null);
563
+
564
+ const scrollToBottom = () => {
565
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
566
+ };
567
+
568
+ useEffect(() => {
569
+ scrollToBottom();
570
+ }, [messages, isThinking]);
571
+
572
+ const handleCancel = () => {
573
+ if (timeoutRef.current) {
574
+ clearTimeout(timeoutRef.current);
575
+ }
576
+ if (intervalRef.current) {
577
+ clearInterval(intervalRef.current);
578
+ }
579
+ setIsThinking(false);
580
+ setIsStreaming(false);
581
+
582
+ // Mark the last message as done if stopped
583
+ setMessages((prev) => {
584
+ const newMessages = [...prev];
585
+ const lastMessage = newMessages[newMessages.length - 1];
586
+ if (lastMessage?.role === 'assistant') {
587
+ lastMessage.done = true;
588
+ }
589
+ return newMessages;
590
+ });
591
+ };
592
+
593
+ const handleSend = (overridePrompt?: string) => {
594
+ const textToSend = overridePrompt || prompt;
595
+
596
+ if (!textToSend.trim()) {
597
+ return;
598
+ }
599
+
600
+ setMessages((prev) => [...prev, { content: textToSend, role: 'user' }]);
601
+ setPrompt('');
602
+ setIsThinking(true);
603
+ const startTime = Date.now();
604
+
605
+ // Simulate network delay (thinking time)
606
+ timeoutRef.current = setTimeout(() => {
607
+ const endTime = Date.now();
608
+ const thinkingTime = endTime - startTime;
609
+ setIsThinking(false);
610
+ setIsStreaming(true);
611
+
612
+ const responseText = FAKE_RESPONSES[responseIndex];
613
+ setResponseIndex((prev) => (prev + 1) % FAKE_RESPONSES.length);
614
+
615
+ let currentText = '';
616
+ setMessages((prev) => [
617
+ ...prev,
618
+ { content: '', done: false, role: 'assistant', thinkingTime },
619
+ ]);
620
+
621
+ // Simulate streaming
622
+ intervalRef.current = setInterval(() => {
623
+ if (currentText.length < responseText.length) {
624
+ // Add a few characters at a time to simulate chunks
625
+ // Ensure we don't split newlines incorrectly if that's an issue,
626
+ // but simple slicing should be fine as long as the source has \n.
627
+ const chunk = responseText.slice(
628
+ currentText.length,
629
+ currentText.length + 2,
630
+ );
631
+ currentText += chunk;
632
+
633
+ setMessages((prev) => {
634
+ const newMessages = [...prev];
635
+ const lastMessage = newMessages[newMessages.length - 1];
636
+ if (lastMessage.role === 'assistant') {
637
+ lastMessage.content = currentText;
638
+ }
639
+ return newMessages;
640
+ });
641
+ } else {
642
+ if (intervalRef.current) {
643
+ clearInterval(intervalRef.current);
644
+ }
645
+ setIsStreaming(false);
646
+ setMessages((prev) => {
647
+ const newMessages = [...prev];
648
+ const lastMessage = newMessages[newMessages.length - 1];
649
+ if (lastMessage.role === 'assistant') {
650
+ lastMessage.done = true;
651
+ }
652
+ return newMessages;
653
+ });
654
+ }
655
+ }, 50);
656
+ }, 2000);
657
+ };
658
+
659
+ return (
660
+ <section className="n-h-screen">
661
+ <div className="n-w-[440px] n-h-full n-flex n-flex-col n-bg-neutral-bg-weak">
662
+ <div className="n-flex n-flex-row n-border-b n-border-neutral-border-weak n-p-3">
663
+ <div className="n-ml-auto">
664
+ <CleanIconButton description="settings" tooltipProps={{}}>
665
+ <Cog6ToothIconOutline />
666
+ </CleanIconButton>
667
+ <CleanIconButton description="close">
668
+ <XMarkIconOutline />
669
+ </CleanIconButton>
670
+ </div>
671
+ </div>
672
+ <div className="n-p-4 n-flex n-flex-col n-grow n-overflow-y-auto">
673
+ {messages.length === 0 ? (
674
+ <div className="n-flex n-flex-col ">
675
+ <div className="n-flex n-flex-col n-gap-12">
676
+ <Typography variant="display">
677
+ Hi [User], how can I help you today?
678
+ </Typography>
679
+ <div className="n-flex n-flex-col n-gap-4">
680
+ <Typography variant="body-medium">Suggestions</Typography>
681
+ <Suggestion
682
+ isPrimary
683
+ onClick={() => {
684
+ handleSend('I want to import data');
685
+ }}
686
+ >
687
+ I want to import data
688
+ </Suggestion>
689
+ <Suggestion
690
+ onClick={() => {
691
+ handleSend('Create an AI agent');
692
+ }}
693
+ >
694
+ Create an AI agent
695
+ </Suggestion>
696
+ <Suggestion
697
+ onClick={() => {
698
+ handleSend('Invite project members');
699
+ }}
700
+ >
701
+ Invite project members
702
+ </Suggestion>
703
+ <Suggestion
704
+ onClick={() => {
705
+ handleSend('Generate a report');
706
+ }}
707
+ >
708
+ Generate a report
709
+ </Suggestion>
710
+ </div>
711
+ <Typography variant="body-medium">
712
+ You can also drag and drop files here, or{' '}
713
+ <TextLink as="button" type="internal-underline">
714
+ browse
715
+ </TextLink>
716
+ . Supports CVG, MOV, PDF
717
+ </Typography>
718
+ </div>
719
+ </div>
720
+ ) : (
721
+ <div className="n-flex n-flex-col n-gap-4 n-pb-4">
722
+ {messages.map((msg, idx) => (
723
+ <div
724
+ key={idx}
725
+ className={`n-flex ${
726
+ msg.role === 'user' ? 'n-justify-end' : 'n-justify-start'
727
+ }`}
728
+ >
729
+ {msg.role === 'user' ? (
730
+ <div className="n-max-w-[85%]">
731
+ <UserBubble
732
+ avatarProps={{
733
+ name: 'NM',
734
+ type: 'letters',
735
+ }}
736
+ >
737
+ {msg.content}
738
+ </UserBubble>
739
+ </div>
740
+ ) : (
741
+ <div className="n-w-full n-flex n-flex-col n-gap-2">
742
+ {msg.thinkingTime !== undefined && (
743
+ <Thinking
744
+ isThinking={false}
745
+ thinkingMs={msg.thinkingTime}
746
+ />
747
+ )}
748
+ <div className="n-flex n-flex-col n-gap-2">
749
+ <Response>{msg.content}</Response>
750
+
751
+ {msg.done === true && (
752
+ <div className="n-flex n-flex-row n-gap-1.5">
753
+ <CleanIconButton size="small" description="Dislike">
754
+ <HandThumbDownIconOutline />
755
+ </CleanIconButton>
756
+ <CleanIconButton size="small" description="Re-run">
757
+ <ArrowPathIconOutline />
758
+ </CleanIconButton>
759
+ <CleanIconButton size="small" description="Copy">
760
+ <Square2StackIconOutline />
761
+ </CleanIconButton>
762
+ </div>
763
+ )}
764
+ </div>
765
+ </div>
766
+ )}
767
+ </div>
768
+ ))}
769
+ {isThinking && <Thinking isThinking={true} />}
770
+ <div ref={messagesEndRef} />
771
+ </div>
772
+ )}
773
+ </div>
774
+ <div className="n-px-4 n-pt-4 n-pb-1 n-mt-auto">
775
+ <Prompt
776
+ value={prompt}
777
+ onChange={(e) => setPrompt(e.target.value)}
778
+ onSubmitPrompt={() => handleSend()}
779
+ onCancelPrompt={handleCancel}
780
+ isRunningPrompt={isThinking || isStreaming}
781
+ isSubmitDisabled={
782
+ prompt.length === 0 && !(isThinking || isStreaming)
783
+ }
784
+ bottomContent={
785
+ <CleanIconButton description="Add files" size="small">
786
+ <PlusIconOutline />
787
+ </CleanIconButton>
788
+ }
789
+ />
790
+ </div>
791
+ </div>
792
+ </section>
793
+ );
794
+ };
795
+
796
+ export default Component;
797
+ ```
798
+
799
+ ### Headings
800
+
801
+ ```tsx
802
+ import { Response } from '@neo4j-ndl/react/ai';
803
+
804
+ const headingsMarkdown = `
805
+ # Heading Level 1
806
+ This is content under heading 1.
807
+
808
+ ## Heading Level 2
809
+ This is content under heading 2.
810
+
811
+ ### Heading Level 3
812
+ This is content under heading 3.
813
+
814
+ #### Heading Level 4
815
+ This is content under heading 4.
816
+
817
+ ##### Heading Level 5
818
+ This is content under heading 5.
819
+
820
+ ###### Heading Level 6
821
+ This is content under heading 6.
822
+
823
+ ---
824
+
825
+ ## Document Structure Example
826
+
827
+ # Main Document Title
828
+
829
+ Brief introduction to the document.
830
+
831
+ ## Introduction
832
+
833
+ This section provides an overview of what we'll cover.
834
+
835
+ ### Background
836
+
837
+ Some background information about the topic.
838
+
839
+ #### Historical Context
840
+
841
+ Details about the history.
842
+
843
+ ##### Key Events
844
+
845
+ - Event 1
846
+ - Event 2
847
+ - Event 3
848
+
849
+ ###### Notes
850
+
851
+ Additional notes and references.
852
+
853
+ ## Content Section
854
+
855
+ Main content goes here.
856
+
857
+ ### Subsection A
858
+
859
+ Details for subsection A.
860
+
861
+ ### Subsection B
862
+
863
+ Details for subsection B.
864
+
865
+ ## Conclusion
866
+
867
+ Final thoughts and summary.
868
+
869
+ ---
870
+
871
+ ## Headings with Other Elements
872
+
873
+ ### Code Example
874
+
875
+ \`\`\`javascript
876
+ console.log("Hello from heading section");
877
+ \`\`\`
878
+
879
+ ### List Example
880
+
881
+ - Item 1
882
+ - Item 2
883
+ - Item 3
884
+
885
+ ### Quote Example
886
+
887
+ > "This is a quote under a heading."
888
+
889
+ ### Mixed Content
890
+
891
+ Regular paragraph with **bold** and *italic* text.
892
+ `;
893
+
894
+ export const Component = () => {
895
+ return (
896
+ <div className="max-w-4xl">
897
+ <Response>{headingsMarkdown}</Response>
898
+ </div>
899
+ );
900
+ };
901
+
902
+ export default Component;
903
+ ```
904
+
905
+ ### Lists
906
+
907
+ ```tsx
908
+ import { Response } from '@neo4j-ndl/react/ai';
909
+
910
+ const listsMarkdown = `
911
+ # Lists Examples
912
+
913
+ ## Simple Unordered List
914
+
915
+ - Apple
916
+ - Banana
917
+ - Cherry
918
+ - Date
919
+ - Elderberry
920
+
921
+ ## Simple Ordered List
922
+
923
+ 1. First step
924
+ 2. Second step
925
+ 3. Third step
926
+ 4. Fourth step
927
+ 5. Fifth step
928
+
929
+ ## Nested Unordered Lists
930
+
931
+ - Fruits
932
+ - Apples
933
+ - Granny Smith
934
+ - Honeycrisp
935
+ - Fuji
936
+ - Oranges
937
+ - Navel
938
+ - Blood Orange
939
+ - Berries
940
+ - Strawberry
941
+ - Blueberry
942
+ - Raspberry
943
+ - Vegetables
944
+ - Leafy Greens
945
+ - Spinach
946
+ - Kale
947
+ - Root Vegetables
948
+ - Carrot
949
+ - Potato
950
+
951
+ ## Nested Ordered Lists
952
+
953
+ 1. Introduction
954
+ 1. Overview
955
+ 2. Purpose
956
+ 3. Scope
957
+ 2. Main Content
958
+ 1. Chapter 1
959
+ 1. Section 1.1
960
+ 2. Section 1.2
961
+ 3. Section 1.3
962
+ 2. Chapter 2
963
+ 1. Section 2.1
964
+ 2. Section 2.2
965
+ 3. Conclusion
966
+ 1. Summary
967
+ 2. Recommendations
968
+
969
+ ## Mixed Lists
970
+
971
+ 1. Install dependencies
972
+ - Node.js
973
+ - npm or yarn
974
+ - Git
975
+ 2. Clone repository
976
+ - \`git clone <url>\`
977
+ - \`cd project-name\`
978
+ 3. Configure settings
979
+ 1. Copy \`.env.example\` to \`.env\`
980
+ 2. Update configuration values:
981
+ - Database URL
982
+ - API keys
983
+ - Port number
984
+ 4. Start development server
985
+ - Run \`npm install\`
986
+ - Run \`npm start\`
987
+
988
+ ## Lists with Paragraphs
989
+
990
+ - **First Item**
991
+
992
+ This is a detailed explanation of the first item. It can contain multiple sentences and provide context.
993
+
994
+ - **Second Item**
995
+
996
+ The second item also has a detailed explanation. This helps provide more information to the reader.
997
+
998
+ - **Third Item**
999
+
1000
+ Each item can have as much detail as needed.
1001
+
1002
+ ## Lists with Code
1003
+
1004
+ - JavaScript example:
1005
+
1006
+ \`\`\`javascript
1007
+ const items = ['a', 'b', 'c'];
1008
+ items.forEach(item => console.log(item));
1009
+ \`\`\`
1010
+
1011
+ - Python example:
1012
+
1013
+ \`\`\`python
1014
+ items = ['a', 'b', 'c']
1015
+ for item in items:
1016
+ print(item)
1017
+ \`\`\`
1018
+
1019
+ ## Task Lists Representation
1020
+
1021
+ - [x] Complete feature A
1022
+ - [x] Write tests
1023
+ - [ ] Review code
1024
+ - [ ] Deploy to production
1025
+ - [ ] Monitor metrics
1026
+
1027
+ ## Lists with Links
1028
+
1029
+ 1. Official Resources
1030
+ - [Neo4j Documentation](https://neo4j.com/docs)
1031
+ - [Neo4j Community](https://community.neo4j.com)
1032
+ - [Neo4j GitHub](https://github.com/neo4j)
1033
+ 2. Tutorials
1034
+ - [Getting Started Guide](https://neo4j.com/start)
1035
+ - [Cypher Query Language](https://neo4j.com/cypher)
1036
+ 3. Tools
1037
+ - Neo4j Desktop
1038
+ - Neo4j Browser
1039
+ - Neo4j Bloom
1040
+
1041
+ ## Lists with Inline Formatting
1042
+
1043
+ - **Bold item** with regular text
1044
+ - *Italic item* with regular text
1045
+ - \`Code item\` with regular text
1046
+ - Regular text with **bold**, *italic*, and \`code\`
1047
+ - Combination: **bold and *italic* text** together
1048
+ `;
1049
+
1050
+ export const Component = () => {
1051
+ return (
1052
+ <div className="max-w-4xl">
1053
+ <Response>{listsMarkdown}</Response>
1054
+ </div>
1055
+ );
1056
+ };
1057
+
1058
+ export default Component;
1059
+ ```
1060
+
1061
+ ### Streaming
1062
+
1063
+ ```tsx
1064
+ import { Response } from '@neo4j-ndl/react/ai';
1065
+ import { useEffect, useState } from 'react';
1066
+
1067
+ const StreamingComponent = () => {
1068
+ const [content, setContent] = useState('');
1069
+
1070
+ useEffect(() => {
1071
+ const fullContent = `
1072
+ # Streaming Response
1073
+
1074
+ This content is being **streamed (and here's a long long long long bold text)** to simulate an AI response.
1075
+
1076
+ It handles:
1077
+ 1. Partial updates
1078
+ 2. Incomplete markdown
1079
+ 3. Code blocks
1080
+
1081
+ \`\`\`javascript
1082
+ function stream() {
1083
+ return "streaming...";
1084
+ }
1085
+ \`\`\`
1086
+ And here comes more text after the code block.`;
1087
+
1088
+ let currentIndex = 0;
1089
+ const interval = setInterval(() => {
1090
+ if (currentIndex < fullContent.length) {
1091
+ setContent(fullContent.slice(0, currentIndex + 1));
1092
+ currentIndex++;
1093
+ } else {
1094
+ clearInterval(interval);
1095
+ }
1096
+ }, 30);
1097
+
1098
+ return () => clearInterval(interval);
1099
+ }, []);
1100
+
1101
+ return <Response>{content}</Response>;
1102
+ };
1103
+
1104
+ export const Component = () => {
1105
+ return <StreamingComponent />;
1106
+ };
1107
+
1108
+ export default Component;
1109
+ ```