@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
1,110 lines (866 loc) • 23.9 kB
Markdown
# Response
Import: `import { Response } from '@neo4j-ndl/react/ai'`
## Props
### Response
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `string` | ✅ | | The response from the AI |
| `isAnimating` | `boolean` | | | Whether the response is animating |
| `ref` | `Ref<HTMLDivElement>` | | | A ref to apply to the root element. |
## Examples
### Default
```tsx
import { Response } from '@neo4j-ndl/react/ai';
const markdownContent = `
# Hello World
This is a **bold** text and this is an *italic* text.
Here is a list:
- Item 1
- Item 2
- Item 3
And a code block:
\`\`\`typescript
const greeting = "Hello World";
console.log(greeting);
\`\`\`
[Link to Neo4j](https://neo4j.com)
`;
export const Component = () => {
return <Response>{markdownContent}</Response>;
};
export default Component;
```
### All Components
```tsx
import { Response } from '@neo4j-ndl/react/ai';
const comprehensiveMarkdown = `
# All Markdown Components Demo
This story demonstrates all the markdown components that the Response component can render.
## Headings
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
## Text Formatting
This is **bold text** and this is *italic text*.
You can also use __bold__ and _italic_ with underscores.
Combine them: **bold and *italic* together**.
## Lists
### Unordered Lists
- Item 1
- Item 2
- Item 3
- Nested item 1
- Nested item 2
- Deeply nested item
- <h1>Nested nested heading</h1>
### Ordered Lists
1. First item
2. Second item
3. Third item
1. Nested numbered item
2. Another nested item
3. <h2>Nested nested heading</h2>
## Code
### Inline Code
Use \`const x = 5\` for inline code.
### Code Blocks
#### JavaScript
\`\`\`javascript
const greeting = "Hello World";
console.log(greeting);
function sayHello(name) {
return \`Hello, \${name}!\`;
}
\`\`\`
#### Python
\`\`\`python
def greet(name):
return f"Hello, {name}!"
print(greet("World"))
\`\`\`
#### Cypher
\`\`\`cypher
MATCH (n:Person)-[:KNOWS]->(m:Person)
WHERE n.name = "Alice"
RETURN m.name
\`\`\`
#### TypeScript
\`\`\`typescript
interface User {
name: string;
age: number;
}
const user: User = {
name: "John",
age: 30
};
\`\`\`
#### Without Language Specification
\`\`\`
Plain code block
without syntax highlighting
\`\`\`
## Links
Visit [Neo4j](https://neo4j.com) for more information.
Multiple links: [Documentation](https://neo4j.com/docs) and [Community](https://community.neo4j.com)
## Blockquotes
> This is a blockquote.
> It can span multiple lines.
> ### Blockquote with heading
>
> You can also include other markdown inside blockquotes.
>
> - Like lists
> - And other elements
### Nested Blockquotes
> Outer quote
>> Nested quote
>>> Deeply nested quote
## Horizontal Rules
Content above the rule
---
Content below the rule
## Images

## Paragraphs
This is a paragraph with multiple sentences. It demonstrates how regular text is rendered. You can write long-form content that spans multiple lines.
Another paragraph here. Paragraphs are separated by blank lines in markdown.
## Mixed Content Example
Here's a practical example combining multiple elements:
### Database Query Tutorial
To query your Neo4j database, follow these steps:
1. **Connect to your database**
Use the following code:
\`\`\`javascript
const driver = neo4j.driver(
'bolt://localhost:7687',
neo4j.auth.basic('neo4j', 'password')
);
\`\`\`
2. **Write your Cypher query**
\`\`\`cypher
MATCH (n:Movie)
WHERE n.released > 2000
RETURN n.title, n.released
ORDER BY n.released DESC
LIMIT 10
\`\`\`
3. **Execute and process results**
> **Note**: Always close your session after use!
For more information, visit the [Neo4j Documentation](https://neo4j.com/docs).
---
## Special Characters and Escaping
You can escape special characters: \\* \\_ \\[ \\]
## Preformatted Text
Use code blocks for preformatted text with preserved spacing:
\`\`\`
This text
has preserved
indentation
\`\`\`
| Item | In Stock | Price |
| :---------------- | :------: | ----: |
| Python Hat | True | 222222223.99 |
| SQL Hat | True | 23.99 |
| Codecademy Tee | False | 19.99 |
| Codecademy Hoodie | False | 42.99 |
`;
export const Component = () => {
return (
<div className="max-w-4xl">
<Response>{comprehensiveMarkdown}</Response>
</div>
);
};
export default Component;
```
### Code Examples
```tsx
import { Response } from '@neo4j-ndl/react/ai';
const codeExamplesMarkdown = `
# Code Examples
This story focuses on different code rendering capabilities.
## Multiple Programming Languages
### JavaScript
\`\`\`javascript
// Array methods
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log('Doubled:', doubled);
console.log('Sum:', sum);
\`\`\`
### TypeScript
\`\`\`typescript
// Generics
function identity<T>(arg: T): T {
return arg;
}
const result = identity<string>("Hello");
const num = identity(42);
\`\`\`
### Python
\`\`\`python
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
# Dictionary comprehension
square_dict = {x: x**2 for x in range(5)}
print(f"Squares: {squares}")
print(f"Evens: {evens}")
\`\`\`
### Cypher
\`\`\`cypher
// Create nodes and relationships
CREATE (alice:Person {name: 'Alice', age: 30})
CREATE (bob:Person {name: 'Bob', age: 25})
CREATE (alice)-[:KNOWS {since: 2020}]->(bob)
RETURN alice, bob
\`\`\`
### SQL
\`\`\`sql
-- Complex query
SELECT
u.name,
COUNT(o.id) as order_count,
SUM(o.total) as total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.active = true
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5
ORDER BY total_spent DESC
LIMIT 10;
\`\`\`
### Rust
\`\`\`rust
// Pattern matching
fn describe_number(n: i32) -> String {
match n {
0 => "zero".to_string(),
1..=10 => "small".to_string(),
11..=100 => "medium".to_string(),
_ => "large".to_string(),
}
}
\`\`\`
### Go
\`\`\`go
// Goroutines and channels
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("worker %d processing job %d\\n", id, j)
results <- j * 2
}
}
\`\`\`
### JSON
\`\`\`json
{
"name": "Response Component",
"version": "1.0.0",
"features": [
"markdown rendering",
"syntax highlighting",
"streaming support"
],
"config": {
"theme": "dark",
"lineNumbers": true
}
}
\`\`\`
### YAML
\`\`\`yaml
# Configuration file
app:
name: MyApp
version: 1.0.0
port: 3000
database:
host: localhost
port: 5432
credentials:
username: admin
password: secret
features:
- authentication
- logging
- caching
\`\`\`
### Shell/Bash
\`\`\`bash
#!/bin/bash
# Deploy script
echo "Starting deployment..."
npm install
npm run build
npm test
if [ $? -eq 0 ]; then
echo "Deployment successful!"
else
echo "Deployment failed!"
exit 1
fi
\`\`\`
## Inline Code
You can use inline code like \`const x = 5\`, \`npm install\`, or \`SELECT * FROM users\` within sentences.
Mixed with text: The \`Array.map()\` method creates a new array and the \`filter()\` method filters elements.
## Code Without Language
\`\`\`
Plain text code block
No syntax highlighting
Just monospace font
\`\`\`
## Long Code Blocks
\`\`\`javascript
// Complex React component
import React, { useState, useEffect, useCallback } from 'react';
const UserDashboard = ({ userId }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchUser = useCallback(async () => {
try {
setLoading(true);
const response = await fetch(\`/api/users/\${userId}\`);
if (!response.ok) throw new Error('Failed to fetch user');
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [userId]);
useEffect(() => {
fetchUser();
}, [fetchUser]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user found</div>;
return (
<div className="dashboard">
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
<button onClick={fetchUser}>Refresh</button>
</div>
);
};
export default UserDashboard;
\`\`\`
`;
export const Component = () => {
return (
<div className="max-w-4xl">
<Response>{codeExamplesMarkdown}</Response>
</div>
);
};
export default Component;
```
### Full Example
```tsx
import { CleanIconButton, TextLink, Typography } from '@neo4j-ndl/react';
import {
Prompt,
Response,
Suggestion,
Thinking,
UserBubble,
} from '@neo4j-ndl/react/ai';
import {
ArrowPathIconOutline,
Cog6ToothIconOutline,
HandThumbDownIconOutline,
PlusIconOutline,
Square2StackIconOutline,
XMarkIconOutline,
} from '@neo4j-ndl/react/icons';
import { useEffect, useRef, useState } from 'react';
const FAKE_RESPONSES = [
`Here is a simple response with some **bold text** and *italics*.`,
`Here is a list of items:
- Item 1
- Item 2
- Item 3`,
`Here is a code block example:
\`\`\`typescript
const greeting = "Hello World";
console.log(greeting);
\`\`\`
`,
`# Heading 1
## Heading 2
### Heading 3
Some text under headings.`,
`You can also use tables:
| Header 1 | Header 2 |
|Data 1|Data 2|
|Data 3|Data 4|
`,
];
export const Component = () => {
const [messages, setMessages] = useState<
{
role: 'user' | 'assistant';
content: string;
thinkingTime?: number;
done?: boolean;
}[]
>([]);
const [prompt, setPrompt] = useState('');
const [isThinking, setIsThinking] = useState(false);
const [isStreaming, setIsStreaming] = useState(false);
const [responseIndex, setResponseIndex] = useState(0);
const messagesEndRef = useRef<HTMLDivElement>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages, isThinking]);
const handleCancel = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
setIsThinking(false);
setIsStreaming(false);
// Mark the last message as done if stopped
setMessages((prev) => {
const newMessages = [...prev];
const lastMessage = newMessages[newMessages.length - 1];
if (lastMessage?.role === 'assistant') {
lastMessage.done = true;
}
return newMessages;
});
};
const handleSend = (overridePrompt?: string) => {
const textToSend = overridePrompt || prompt;
if (!textToSend.trim()) {
return;
}
setMessages((prev) => [...prev, { content: textToSend, role: 'user' }]);
setPrompt('');
setIsThinking(true);
const startTime = Date.now();
// Simulate network delay (thinking time)
timeoutRef.current = setTimeout(() => {
const endTime = Date.now();
const thinkingTime = endTime - startTime;
setIsThinking(false);
setIsStreaming(true);
const responseText = FAKE_RESPONSES[responseIndex];
setResponseIndex((prev) => (prev + 1) % FAKE_RESPONSES.length);
let currentText = '';
setMessages((prev) => [
...prev,
{ content: '', done: false, role: 'assistant', thinkingTime },
]);
// Simulate streaming
intervalRef.current = setInterval(() => {
if (currentText.length < responseText.length) {
// Add a few characters at a time to simulate chunks
// Ensure we don't split newlines incorrectly if that's an issue,
// but simple slicing should be fine as long as the source has \n.
const chunk = responseText.slice(
currentText.length,
currentText.length + 2,
);
currentText += chunk;
setMessages((prev) => {
const newMessages = [...prev];
const lastMessage = newMessages[newMessages.length - 1];
if (lastMessage.role === 'assistant') {
lastMessage.content = currentText;
}
return newMessages;
});
} else {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
setIsStreaming(false);
setMessages((prev) => {
const newMessages = [...prev];
const lastMessage = newMessages[newMessages.length - 1];
if (lastMessage.role === 'assistant') {
lastMessage.done = true;
}
return newMessages;
});
}
}, 50);
}, 2000);
};
return (
<section className="n-h-screen">
<div className="n-w-[440px] n-h-full n-flex n-flex-col n-bg-neutral-bg-weak">
<div className="n-flex n-flex-row n-border-b n-border-neutral-border-weak n-p-3">
<div className="n-ml-auto">
<CleanIconButton description="settings" tooltipProps={{}}>
<Cog6ToothIconOutline />
</CleanIconButton>
<CleanIconButton description="close">
<XMarkIconOutline />
</CleanIconButton>
</div>
</div>
<div className="n-p-4 n-flex n-flex-col n-grow n-overflow-y-auto">
{messages.length === 0 ? (
<div className="n-flex n-flex-col ">
<div className="n-flex n-flex-col n-gap-12">
<Typography variant="display">
Hi [User], how can I help you today?
</Typography>
<div className="n-flex n-flex-col n-gap-4">
<Typography variant="body-medium">Suggestions</Typography>
<Suggestion
isPrimary
onClick={() => {
handleSend('I want to import data');
}}
>
I want to import data
</Suggestion>
<Suggestion
onClick={() => {
handleSend('Create an AI agent');
}}
>
Create an AI agent
</Suggestion>
<Suggestion
onClick={() => {
handleSend('Invite project members');
}}
>
Invite project members
</Suggestion>
<Suggestion
onClick={() => {
handleSend('Generate a report');
}}
>
Generate a report
</Suggestion>
</div>
<Typography variant="body-medium">
You can also drag and drop files here, or{' '}
<TextLink as="button" type="internal-underline">
browse
</TextLink>
. Supports CVG, MOV, PDF
</Typography>
</div>
</div>
) : (
<div className="n-flex n-flex-col n-gap-4 n-pb-4">
{messages.map((msg, idx) => (
<div
key={idx}
className={`n-flex ${
msg.role === 'user' ? 'n-justify-end' : 'n-justify-start'
}`}
>
{msg.role === 'user' ? (
<div className="n-max-w-[85%]">
<UserBubble
avatarProps={{
name: 'NM',
type: 'letters',
}}
>
{msg.content}
</UserBubble>
</div>
) : (
<div className="n-w-full n-flex n-flex-col n-gap-2">
{msg.thinkingTime !== undefined && (
<Thinking
isThinking={false}
thinkingMs={msg.thinkingTime}
/>
)}
<div className="n-flex n-flex-col n-gap-2">
<Response>{msg.content}</Response>
{msg.done === true && (
<div className="n-flex n-flex-row n-gap-1.5">
<CleanIconButton size="small" description="Dislike">
<HandThumbDownIconOutline />
</CleanIconButton>
<CleanIconButton size="small" description="Re-run">
<ArrowPathIconOutline />
</CleanIconButton>
<CleanIconButton size="small" description="Copy">
<Square2StackIconOutline />
</CleanIconButton>
</div>
)}
</div>
</div>
)}
</div>
))}
{isThinking && <Thinking isThinking={true} />}
<div ref={messagesEndRef} />
</div>
)}
</div>
<div className="n-px-4 n-pt-4 n-pb-1 n-mt-auto">
<Prompt
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onSubmitPrompt={() => handleSend()}
onCancelPrompt={handleCancel}
isRunningPrompt={isThinking || isStreaming}
isSubmitDisabled={
prompt.length === 0 && !(isThinking || isStreaming)
}
bottomContent={
<CleanIconButton description="Add files" size="small">
<PlusIconOutline />
</CleanIconButton>
}
/>
</div>
</div>
</section>
);
};
export default Component;
```
### Headings
```tsx
import { Response } from '@neo4j-ndl/react/ai';
const headingsMarkdown = `
# Heading Level 1
This is content under heading 1.
## Heading Level 2
This is content under heading 2.
### Heading Level 3
This is content under heading 3.
#### Heading Level 4
This is content under heading 4.
##### Heading Level 5
This is content under heading 5.
###### Heading Level 6
This is content under heading 6.
---
## Document Structure Example
# Main Document Title
Brief introduction to the document.
## Introduction
This section provides an overview of what we'll cover.
### Background
Some background information about the topic.
#### Historical Context
Details about the history.
##### Key Events
- Event 1
- Event 2
- Event 3
###### Notes
Additional notes and references.
## Content Section
Main content goes here.
### Subsection A
Details for subsection A.
### Subsection B
Details for subsection B.
## Conclusion
Final thoughts and summary.
---
## Headings with Other Elements
### Code Example
\`\`\`javascript
console.log("Hello from heading section");
\`\`\`
### List Example
- Item 1
- Item 2
- Item 3
### Quote Example
> "This is a quote under a heading."
### Mixed Content
Regular paragraph with **bold** and *italic* text.
`;
export const Component = () => {
return (
<div className="max-w-4xl">
<Response>{headingsMarkdown}</Response>
</div>
);
};
export default Component;
```
### Lists
```tsx
import { Response } from '@neo4j-ndl/react/ai';
const listsMarkdown = `
# Lists Examples
## Simple Unordered List
- Apple
- Banana
- Cherry
- Date
- Elderberry
## Simple Ordered List
1. First step
2. Second step
3. Third step
4. Fourth step
5. Fifth step
## Nested Unordered Lists
- Fruits
- Apples
- Granny Smith
- Honeycrisp
- Fuji
- Oranges
- Navel
- Blood Orange
- Berries
- Strawberry
- Blueberry
- Raspberry
- Vegetables
- Leafy Greens
- Spinach
- Kale
- Root Vegetables
- Carrot
- Potato
## Nested Ordered Lists
1. Introduction
1. Overview
2. Purpose
3. Scope
2. Main Content
1. Chapter 1
1. Section 1.1
2. Section 1.2
3. Section 1.3
2. Chapter 2
1. Section 2.1
2. Section 2.2
3. Conclusion
1. Summary
2. Recommendations
## Mixed Lists
1. Install dependencies
- Node.js
- npm or yarn
- Git
2. Clone repository
- \`git clone <url>\`
- \`cd project-name\`
3. Configure settings
1. Copy \`.env.example\` to \`.env\`
2. Update configuration values:
- Database URL
- API keys
- Port number
4. Start development server
- Run \`npm install\`
- Run \`npm start\`
## Lists with Paragraphs
- **First Item**
This is a detailed explanation of the first item. It can contain multiple sentences and provide context.
- **Second Item**
The second item also has a detailed explanation. This helps provide more information to the reader.
- **Third Item**
Each item can have as much detail as needed.
## Lists with Code
- JavaScript example:
\`\`\`javascript
const items = ['a', 'b', 'c'];
items.forEach(item => console.log(item));
\`\`\`
- Python example:
\`\`\`python
items = ['a', 'b', 'c']
for item in items:
print(item)
\`\`\`
## Task Lists Representation
- [x] Complete feature A
- [x] Write tests
- [ ] Review code
- [ ] Deploy to production
- [ ] Monitor metrics
## Lists with Links
1. Official Resources
- [Neo4j Documentation](https://neo4j.com/docs)
- [Neo4j Community](https://community.neo4j.com)
- [Neo4j GitHub](https://github.com/neo4j)
2. Tutorials
- [Getting Started Guide](https://neo4j.com/start)
- [Cypher Query Language](https://neo4j.com/cypher)
3. Tools
- Neo4j Desktop
- Neo4j Browser
- Neo4j Bloom
## Lists with Inline Formatting
- **Bold item** with regular text
- *Italic item* with regular text
- \`Code item\` with regular text
- Regular text with **bold**, *italic*, and \`code\`
- Combination: **bold and *italic* text** together
`;
export const Component = () => {
return (
<div className="max-w-4xl">
<Response>{listsMarkdown}</Response>
</div>
);
};
export default Component;
```
### Streaming
```tsx
import { Response } from '@neo4j-ndl/react/ai';
import { useEffect, useState } from 'react';
const StreamingComponent = () => {
const [content, setContent] = useState('');
useEffect(() => {
const fullContent = `
# Streaming Response
This content is being **streamed (and here's a long long long long bold text)** to simulate an AI response.
It handles:
1. Partial updates
2. Incomplete markdown
3. Code blocks
\`\`\`javascript
function stream() {
return "streaming...";
}
\`\`\`
And here comes more text after the code block.`;
let currentIndex = 0;
const interval = setInterval(() => {
if (currentIndex < fullContent.length) {
setContent(fullContent.slice(0, currentIndex + 1));
currentIndex++;
} else {
clearInterval(interval);
}
}, 30);
return () => clearInterval(interval);
}, []);
return <Response>{content}</Response>;
};
export const Component = () => {
return <StreamingComponent />;
};
export default Component;
```