mirror of
https://github.com/run-llama/chat-ui.git
synced 2026-07-21 11:25:22 -04:00
feat: support canvas (#75)
* use light theme for code block * enhance codeblock style to compatity with new white UI * feat: move canvas to chat-ui * fix: format * fix imports * copy markdown styles before run dev * Create swift-flies-breathe.md * show ArtifactAnnotations by default * add canvas demo * make it responsive
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@llamaindex/chat-ui': patch
|
||||
'@llamaindex/eslint-config': patch
|
||||
---
|
||||
|
||||
feat: support canvas
|
||||
@@ -0,0 +1,586 @@
|
||||
'use client'
|
||||
|
||||
import { useCopyToClipboard } from '@/app/use-copy-to-clipboard'
|
||||
import {
|
||||
ChatCanvas,
|
||||
ChatInput,
|
||||
ChatMessages,
|
||||
ChatSection,
|
||||
} from '@llamaindex/chat-ui'
|
||||
import { Message, useChat } from 'ai/react'
|
||||
|
||||
const code = `
|
||||
import {
|
||||
ChatCanvas,
|
||||
ChatInput,
|
||||
ChatMessages,
|
||||
ChatSection,
|
||||
} from '@llamaindex/chat-ui'
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
export function CustomChat() {
|
||||
const handler = useChat({ initialMessages: [] })
|
||||
|
||||
return (
|
||||
<ChatSection
|
||||
handler={handler}
|
||||
className="block h-screen flex-row gap-4 p-0 md:flex md:p-5"
|
||||
>
|
||||
<div className="md:max-w-1/2 mx-auto flex h-full min-w-0 max-w-full flex-1 flex-col gap-4">
|
||||
<ChatMessages />
|
||||
<ChatInput />
|
||||
</div>
|
||||
<ChatCanvas className="w-full md:w-2/3" />
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
const initialMessages: Message[] = [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Write binary search algorithm in python',
|
||||
id: 'aeJ4ZOWlhUA2R4vg',
|
||||
},
|
||||
{
|
||||
id: 'Yqc9VoIR3ANyzTj3',
|
||||
role: 'assistant',
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
annotations: [
|
||||
{
|
||||
userInput: 'Write binary search algorithm in python',
|
||||
chatHistory: [],
|
||||
},
|
||||
{
|
||||
input: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Write binary search algorithm in python',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
input: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
"You are a helpful assistant that generates code artifacts or document artifacts based on the user's request. Do NOT include installation instructions in the response. Please add a short summary about the artifact, don't include any other text or repeat the code or document",
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Write binary search algorithm in python',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
agentName: 'Agent',
|
||||
response: {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
options: {
|
||||
toolCall: [
|
||||
{
|
||||
name: 'code_artifact_generator',
|
||||
input: {
|
||||
requirement:
|
||||
'Binary search algorithm in Python that searches for a target value in a sorted list.',
|
||||
},
|
||||
id: 'call_2zAd9vJV9wFUDwEBf6aypLyc',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
toolCalls: [
|
||||
{
|
||||
data: {
|
||||
agentName: 'Agent',
|
||||
toolName: 'code_artifact_generator',
|
||||
toolKwargs: {
|
||||
requirement:
|
||||
'Binary search algorithm in Python that searches for a target value in a sorted list.',
|
||||
},
|
||||
toolId: 'call_2zAd9vJV9wFUDwEBf6aypLyc',
|
||||
},
|
||||
displayName: 'AgentToolCall',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
response: {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
options: {
|
||||
toolCall: [
|
||||
{
|
||||
name: 'code_artifact_generator',
|
||||
input: {
|
||||
requirement:
|
||||
'Binary search algorithm in Python that searches for a target value in a sorted list.',
|
||||
},
|
||||
id: 'call_2zAd9vJV9wFUDwEBf6aypLyc',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
toolCalls: [
|
||||
{
|
||||
data: {
|
||||
agentName: 'Agent',
|
||||
toolName: 'code_artifact_generator',
|
||||
toolKwargs: {
|
||||
requirement:
|
||||
'Binary search algorithm in Python that searches for a target value in a sorted list.',
|
||||
},
|
||||
toolId: 'call_2zAd9vJV9wFUDwEBf6aypLyc',
|
||||
},
|
||||
displayName: 'AgentToolCall',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
agentName: 'Agent',
|
||||
toolCalls: [
|
||||
{
|
||||
data: {
|
||||
agentName: 'Agent',
|
||||
toolName: 'code_artifact_generator',
|
||||
toolKwargs: {
|
||||
requirement:
|
||||
'Binary search algorithm in Python that searches for a target value in a sorted list.',
|
||||
},
|
||||
toolId: 'call_2zAd9vJV9wFUDwEBf6aypLyc',
|
||||
},
|
||||
displayName: 'AgentToolCall',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'agent',
|
||||
data: {
|
||||
agent: 'Agent',
|
||||
text: 'Using tool: \'code_artifact_generator\' with inputs: \'{"requirement":"Binary search algorithm in Python that searches for a target value in a sorted list."}\'',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
agentName: 'Agent',
|
||||
results: [
|
||||
{
|
||||
data: {
|
||||
toolName: 'code_artifact_generator',
|
||||
toolKwargs: {
|
||||
requirement:
|
||||
'Binary search algorithm in Python that searches for a target value in a sorted list.',
|
||||
},
|
||||
toolId: 'call_2zAd9vJV9wFUDwEBf6aypLyc',
|
||||
toolOutput: {
|
||||
id: 'call_2zAd9vJV9wFUDwEBf6aypLyc',
|
||||
result:
|
||||
'{\n type: code,\n data: {\n file_name: binary_search.py,\n code: def binary_search(arr, target):\\n left, right = 0, len(arr) - 1\\n while left <= right:\\n mid = left + (right - left) // 2\\n if arr[mid] == target:\\n return mid\\n elif arr[mid] < target:\\n left = mid + 1\\n else:\\n right = mid - 1\\n return -1\\n\\n# Example usage:\\n# sorted_list = [1, 2, 3, 4, 5]\\n# target = 3\\n# result = binary_search(sorted_list, target)\\n# print(result) # Output: 2,\n language: python\n },\n created_at: 1745480281756\n}',
|
||||
isError: false,
|
||||
},
|
||||
returnDirect: false,
|
||||
raw: {
|
||||
type: 'code',
|
||||
data: {
|
||||
file_name: 'binary_search.py',
|
||||
code: 'def binary_search(arr, target):\n left, right = 0, len(arr) - 1\n while left <= right:\n mid = left + (right - left) // 2\n if arr[mid] == target:\n return mid\n elif arr[mid] < target:\n left = mid + 1\n else:\n right = mid - 1\n return -1\n\n# Example usage:\n# sorted_list = [1, 2, 3, 4, 5]\n# target = 3\n# result = binary_search(sorted_list, target)\n# print(result) # Output: 2',
|
||||
language: 'python',
|
||||
},
|
||||
created_at: 1745480281756,
|
||||
},
|
||||
},
|
||||
displayName: 'AgentToolCallResult',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'artifact',
|
||||
data: {
|
||||
type: 'code',
|
||||
data: {
|
||||
file_name: 'binary_search.py',
|
||||
code: 'def binary_search(arr, target):\n left, right = 0, len(arr) - 1\n while left <= right:\n mid = left + (right - left) // 2\n if arr[mid] == target:\n return mid\n elif arr[mid] < target:\n left = mid + 1\n else:\n right = mid - 1\n return -1\n\n# Example usage:\n# sorted_list = [1, 2, 3, 4, 5]\n# target = 3\n# result = binary_search(sorted_list, target)\n# print(result) # Output: 2',
|
||||
language: 'python',
|
||||
},
|
||||
created_at: 1745480281756,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Write binary search algorithm in python',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
input: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
"You are a helpful assistant that generates code artifacts or document artifacts based on the user's request. Do NOT include installation instructions in the response. Please add a short summary about the artifact, don't include any other text or repeat the code or document",
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Write binary search algorithm in python',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
agentName: 'Agent',
|
||||
response: {
|
||||
role: 'assistant',
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
},
|
||||
toolCalls: [],
|
||||
},
|
||||
{
|
||||
response: {
|
||||
role: 'assistant',
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
},
|
||||
toolCalls: [],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
result:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
},
|
||||
{
|
||||
type: 'suggested_questions',
|
||||
data: [
|
||||
'How does the binary search algorithm work?',
|
||||
'Can you provide an example of how to implement binary search in Python?',
|
||||
'What are the time and space complexities of the binary search algorithm?',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Add some logs to the code',
|
||||
annotations: [],
|
||||
id: 'k2FkjYkhd44CC6xw',
|
||||
},
|
||||
{
|
||||
id: 'yWQHYXgn6WT0oE7J',
|
||||
role: 'assistant',
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm that includes logging statements to track the values of the left, right, and mid indices during each step of the search process.',
|
||||
annotations: [
|
||||
{
|
||||
userInput: 'Add some logs to the code',
|
||||
chatHistory: [
|
||||
{
|
||||
content: 'Write binary search algorithm in python',
|
||||
role: 'user',
|
||||
},
|
||||
{
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
role: 'assistant',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
input: [
|
||||
{
|
||||
content: 'Write binary search algorithm in python',
|
||||
role: 'user',
|
||||
},
|
||||
{
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
role: 'assistant',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Add some logs to the code',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
input: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
"You are a helpful assistant that generates code artifacts or document artifacts based on the user's request. Do NOT include installation instructions in the response. Please add a short summary about the artifact, don't include any other text or repeat the code or document",
|
||||
},
|
||||
{
|
||||
content: 'Write binary search algorithm in python',
|
||||
role: 'user',
|
||||
},
|
||||
{
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
role: 'assistant',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Add some logs to the code',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
agentName: 'Agent',
|
||||
response: {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
options: {
|
||||
toolCall: [
|
||||
{
|
||||
name: 'code_artifact_generator',
|
||||
input: {
|
||||
requirement:
|
||||
'Python implementation of the binary search algorithm with logging for each step of the search process.',
|
||||
},
|
||||
id: 'call_5vlAF3SDasVgtH3Wk0EgL2oX',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
toolCalls: [
|
||||
{
|
||||
data: {
|
||||
agentName: 'Agent',
|
||||
toolName: 'code_artifact_generator',
|
||||
toolKwargs: {
|
||||
requirement:
|
||||
'Python implementation of the binary search algorithm with logging for each step of the search process.',
|
||||
},
|
||||
toolId: 'call_5vlAF3SDasVgtH3Wk0EgL2oX',
|
||||
},
|
||||
displayName: 'AgentToolCall',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
response: {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
options: {
|
||||
toolCall: [
|
||||
{
|
||||
name: 'code_artifact_generator',
|
||||
input: {
|
||||
requirement:
|
||||
'Python implementation of the binary search algorithm with logging for each step of the search process.',
|
||||
},
|
||||
id: 'call_5vlAF3SDasVgtH3Wk0EgL2oX',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
toolCalls: [
|
||||
{
|
||||
data: {
|
||||
agentName: 'Agent',
|
||||
toolName: 'code_artifact_generator',
|
||||
toolKwargs: {
|
||||
requirement:
|
||||
'Python implementation of the binary search algorithm with logging for each step of the search process.',
|
||||
},
|
||||
toolId: 'call_5vlAF3SDasVgtH3Wk0EgL2oX',
|
||||
},
|
||||
displayName: 'AgentToolCall',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
agentName: 'Agent',
|
||||
toolCalls: [
|
||||
{
|
||||
data: {
|
||||
agentName: 'Agent',
|
||||
toolName: 'code_artifact_generator',
|
||||
toolKwargs: {
|
||||
requirement:
|
||||
'Python implementation of the binary search algorithm with logging for each step of the search process.',
|
||||
},
|
||||
toolId: 'call_5vlAF3SDasVgtH3Wk0EgL2oX',
|
||||
},
|
||||
displayName: 'AgentToolCall',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'agent',
|
||||
data: {
|
||||
agent: 'Agent',
|
||||
text: 'Using tool: \'code_artifact_generator\' with inputs: \'{"requirement":"Python implementation of the binary search algorithm with logging for each step of the search process."}\'',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
agentName: 'Agent',
|
||||
results: [
|
||||
{
|
||||
data: {
|
||||
toolName: 'code_artifact_generator',
|
||||
toolKwargs: {
|
||||
requirement:
|
||||
'Python implementation of the binary search algorithm with logging for each step of the search process.',
|
||||
},
|
||||
toolId: 'call_5vlAF3SDasVgtH3Wk0EgL2oX',
|
||||
toolOutput: {
|
||||
id: 'call_5vlAF3SDasVgtH3Wk0EgL2oX',
|
||||
result:
|
||||
"{\n type: code,\n data: {\n file_name: binary_search.py,\n code: import logging\\n\\nlogging.basicConfig(level=logging.INFO)\\n\\ndef binary_search(arr, target):\\n left, right = 0, len(arr) - 1\\n while left <= right:\\n mid = left + (right - left) // 2\\n logging.info(f'Left: {left}, Right: {right}, Mid: {mid}, Mid Value: {arr[mid]}')\\n if arr[mid] == target:\\n return mid\\n elif arr[mid] < target:\\n left = mid + 1\\n else:\\n right = mid - 1\\n return -1\\n\\n# Example usage\\nif __name__ == '__main__':\\n arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\\n target = 7\\n result = binary_search(arr, target)\\n if result != -1:\\n logging.info(f'Target found at index: {result}')\\n else:\\n logging.info('Target not found'),\n language: python\n },\n created_at: 1745480319651\n}",
|
||||
isError: false,
|
||||
},
|
||||
returnDirect: false,
|
||||
raw: {
|
||||
type: 'code',
|
||||
data: {
|
||||
file_name: 'binary_search.py',
|
||||
code: "import logging\n\nlogging.basicConfig(level=logging.INFO)\n\ndef binary_search(arr, target):\n left, right = 0, len(arr) - 1\n while left <= right:\n mid = left + (right - left) // 2\n logging.info(f'Left: {left}, Right: {right}, Mid: {mid}, Mid Value: {arr[mid]}')\n if arr[mid] == target:\n return mid\n elif arr[mid] < target:\n left = mid + 1\n else:\n right = mid - 1\n return -1\n\n# Example usage\nif __name__ == '__main__':\n arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n target = 7\n result = binary_search(arr, target)\n if result != -1:\n logging.info(f'Target found at index: {result}')\n else:\n logging.info('Target not found')",
|
||||
language: 'python',
|
||||
},
|
||||
created_at: 1745480319651,
|
||||
},
|
||||
},
|
||||
displayName: 'AgentToolCallResult',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'artifact',
|
||||
data: {
|
||||
type: 'code',
|
||||
data: {
|
||||
file_name: 'binary_search.py',
|
||||
code: "import logging\n\nlogging.basicConfig(level=logging.INFO)\n\ndef binary_search(arr, target):\n left, right = 0, len(arr) - 1\n while left <= right:\n mid = left + (right - left) // 2\n logging.info(f'Left: {left}, Right: {right}, Mid: {mid}, Mid Value: {arr[mid]}')\n if arr[mid] == target:\n return mid\n elif arr[mid] < target:\n left = mid + 1\n else:\n right = mid - 1\n return -1\n\n# Example usage\nif __name__ == '__main__':\n arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n target = 7\n result = binary_search(arr, target)\n if result != -1:\n logging.info(f'Target found at index: {result}')\n else:\n logging.info('Target not found')",
|
||||
language: 'python',
|
||||
},
|
||||
created_at: 1745480319651,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: [
|
||||
{
|
||||
content: 'Write binary search algorithm in python',
|
||||
role: 'user',
|
||||
},
|
||||
{
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
role: 'assistant',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Add some logs to the code',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
input: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
"You are a helpful assistant that generates code artifacts or document artifacts based on the user's request. Do NOT include installation instructions in the response. Please add a short summary about the artifact, don't include any other text or repeat the code or document",
|
||||
},
|
||||
{
|
||||
content: 'Write binary search algorithm in python',
|
||||
role: 'user',
|
||||
},
|
||||
{
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm, which efficiently searches for a target value in a sorted list.',
|
||||
role: 'assistant',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Add some logs to the code',
|
||||
},
|
||||
],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
agentName: 'Agent',
|
||||
response: {
|
||||
role: 'assistant',
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm that includes logging statements to track the values of the left, right, and mid indices during each step of the search process.',
|
||||
},
|
||||
toolCalls: [],
|
||||
},
|
||||
{
|
||||
response: {
|
||||
role: 'assistant',
|
||||
content:
|
||||
'The artifact is a Python implementation of the binary search algorithm that includes logging statements to track the values of the left, right, and mid indices during each step of the search process.',
|
||||
},
|
||||
toolCalls: [],
|
||||
currentAgentName: 'Agent',
|
||||
},
|
||||
{
|
||||
result:
|
||||
'The artifact is a Python implementation of the binary search algorithm that includes logging statements to track the values of the left, right, and mid indices during each step of the search process.',
|
||||
},
|
||||
{
|
||||
type: 'suggested_questions',
|
||||
data: [
|
||||
'Can you explain how the binary search algorithm works?',
|
||||
'What are the time and space complexities of the binary search algorithm?',
|
||||
'How can I modify the binary search algorithm to return the index of the target value?',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default function Page(): JSX.Element {
|
||||
return <CustomChat />
|
||||
}
|
||||
|
||||
function CustomChat() {
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard({ timeout: 2000 })
|
||||
const handler = useChat({ initialMessages })
|
||||
|
||||
return (
|
||||
<ChatSection
|
||||
handler={handler}
|
||||
className="block h-screen flex-row gap-4 p-0 md:flex md:p-5"
|
||||
>
|
||||
<div className="md:max-w-1/2 mx-auto flex h-full min-w-0 max-w-full flex-1 flex-col gap-4">
|
||||
<div className="flex justify-between gap-2 border-b p-4">
|
||||
<div>
|
||||
<h1 className="bg-gradient-to-r from-[#e711dd] to-[#0fc1e0] bg-clip-text text-lg font-bold text-transparent">
|
||||
LlamaIndex ChatUI - Canvas Demo
|
||||
</h1>
|
||||
<p className="text-sm text-zinc-400">
|
||||
Try click to a version to see how it works
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
copyToClipboard(code)
|
||||
}}
|
||||
className={`flex h-10 items-center gap-2 rounded-lg bg-zinc-700 px-2 py-2 text-sm font-medium text-white transition-colors hover:bg-zinc-600 ${
|
||||
isCopied ? 'bg-green-600 hover:bg-green-500' : ''
|
||||
}`}
|
||||
>
|
||||
{isCopied ? 'Copied!' : 'Copy Code'}
|
||||
</button>
|
||||
</div>
|
||||
<ChatMessages />
|
||||
<ChatInput />
|
||||
</div>
|
||||
<ChatCanvas className="w-full md:w-2/3" />
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
@@ -52,6 +52,12 @@ export default function Page(): JSX.Element {
|
||||
>
|
||||
Custom Chat
|
||||
</Link>
|
||||
<Link
|
||||
href="/demo/canvas"
|
||||
className="rounded-full bg-gradient-to-r from-[#ff6b6b] to-[#4ecdc4] px-6 py-2 font-medium text-white transition hover:opacity-90"
|
||||
>
|
||||
Chat with Canvas
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,12 +25,14 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "pnpm run clean && pnpm run build:js && pnpm run build:css && pnpm run copy-fonts",
|
||||
"build": "pnpm run clean && pnpm run build:js && pnpm run prepare:style",
|
||||
"prepare:style": "pnpm run build:css && pnpm run copy-fonts",
|
||||
"build:css": "postcss \"src/styles/*.css\" --base src/styles --dir dist/styles",
|
||||
"build:js": "tsup",
|
||||
"copy-fonts": "cp -r node_modules/katex/dist/fonts dist/styles/fonts",
|
||||
"lint": "eslint src/",
|
||||
"dev": "pnpm run build:js --watch",
|
||||
"predev": "pnpm run prepare:style",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist",
|
||||
"copy": "cp -r ../../README.md ../../LICENSE .",
|
||||
@@ -59,6 +61,8 @@
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.7",
|
||||
"@radix-ui/react-accordion": "^1.2.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"highlight.js": "^11.10.0",
|
||||
@@ -76,4 +80,4 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { JSONValue } from './chat.interface'
|
||||
import { JSONValue, Message } from './chat.interface'
|
||||
|
||||
export enum MessageAnnotationType {
|
||||
IMAGE = 'image',
|
||||
@@ -7,6 +7,7 @@ export enum MessageAnnotationType {
|
||||
EVENTS = 'events',
|
||||
SUGGESTED_QUESTIONS = 'suggested_questions',
|
||||
AGENT_EVENTS = 'agent',
|
||||
ARTIFACT = 'artifact',
|
||||
}
|
||||
|
||||
export type ImageData = {
|
||||
@@ -65,6 +66,24 @@ export type AgentEventData = {
|
||||
|
||||
export type SuggestedQuestionsData = string[]
|
||||
|
||||
export type Artifact<T = unknown> = {
|
||||
created_at: number
|
||||
type: 'code' | 'document'
|
||||
data: T
|
||||
}
|
||||
|
||||
export type CodeArtifact = Artifact<{
|
||||
file_name: string
|
||||
code: string
|
||||
language: string
|
||||
}>
|
||||
|
||||
export type DocumentArtifact = Artifact<{
|
||||
title: string
|
||||
content: string
|
||||
type: string
|
||||
}>
|
||||
|
||||
export type AnnotationData =
|
||||
| ImageData
|
||||
| DocumentFileData
|
||||
@@ -72,6 +91,7 @@ export type AnnotationData =
|
||||
| EventData
|
||||
| AgentEventData
|
||||
| SuggestedQuestionsData
|
||||
| Artifact
|
||||
|
||||
export type MessageAnnotation = {
|
||||
type: MessageAnnotationType
|
||||
@@ -151,3 +171,28 @@ function preprocessSourceNodes(nodes: SourceNode[]): SourceNode[] {
|
||||
})
|
||||
return processedNodes
|
||||
}
|
||||
|
||||
export type CodeArtifactError = {
|
||||
artifact: CodeArtifact
|
||||
errors: string[]
|
||||
}
|
||||
|
||||
export function extractArtifactsFromMessage(message: Message): Artifact[] {
|
||||
const artifacts = getChatUIAnnotation<Artifact>(
|
||||
message.annotations as MessageAnnotation[],
|
||||
MessageAnnotationType.ARTIFACT
|
||||
)
|
||||
return artifacts ?? []
|
||||
}
|
||||
|
||||
// extract artifacts from all messages (sort ascending by created_at)
|
||||
export function extractArtifactsFromAllMessages(messages: Message[]) {
|
||||
return messages
|
||||
.flatMap(extractArtifactsFromMessage)
|
||||
.sort((a, b) => a.created_at - b.created_at)
|
||||
}
|
||||
|
||||
// check if two artifacts are equal by comparing their type and created time
|
||||
export function isEqualArtifact(a: Artifact, b: Artifact) {
|
||||
return a.type === b.type && a.created_at === b.created_at
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
'use client'
|
||||
|
||||
import { Check, Copy, Download, History, X } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useCopyToClipboard } from '../../hook/use-copy-to-clipboard'
|
||||
import { cn } from '../../lib/utils'
|
||||
import { Badge } from '../../ui/badge'
|
||||
import { Button } from '../../ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../../ui/popover'
|
||||
import {
|
||||
Artifact,
|
||||
CodeArtifact,
|
||||
DocumentArtifact,
|
||||
isEqualArtifact,
|
||||
} from '../annotation'
|
||||
import { useChatCanvas } from './context'
|
||||
|
||||
interface ChatCanvasActionsProps {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ChatCanvasActions(props: ChatCanvasActionsProps) {
|
||||
const children = props.children ?? (
|
||||
<>
|
||||
<ArtifactVersionHistory />
|
||||
<ArtifactContentCopy />
|
||||
<ArtifactDownloadButton />
|
||||
<CanvasCloseButton />
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center gap-1', props.className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ArtifactVersionHistory() {
|
||||
const {
|
||||
getArtifactsByType,
|
||||
openArtifactInCanvas,
|
||||
displayedArtifact,
|
||||
getArtifactVersion,
|
||||
restoreArtifact,
|
||||
} = useChatCanvas()
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
if (!displayedArtifact) return null
|
||||
|
||||
const allArtifactsByCurrentType = getArtifactsByType(displayedArtifact.type)
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-8 cursor-pointer rounded-full text-xs"
|
||||
>
|
||||
<History className="mr-1 size-4" />
|
||||
Version {getArtifactVersion(displayedArtifact).versionNumber}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-48 p-0 text-xs" align="end">
|
||||
<h4 className="border-b p-2 px-3 font-semibold">Version History</h4>
|
||||
<div className="max-h-80 overflow-y-auto">
|
||||
{allArtifactsByCurrentType.map((artifact, index) => {
|
||||
const isCurrent = isEqualArtifact(artifact, displayedArtifact)
|
||||
const { versionNumber, isLatest } = getArtifactVersion(artifact)
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="text-muted-foreground flex cursor-pointer items-center justify-between px-3 py-2 hover:bg-gray-100"
|
||||
onClick={() => {
|
||||
openArtifactInCanvas(artifact)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
>
|
||||
<span className={cn(isCurrent && 'text-blue-500')}>
|
||||
Version {versionNumber}
|
||||
</span>
|
||||
{isLatest ? (
|
||||
<Badge className="h-6 w-[70px] justify-center bg-blue-500 text-center hover:bg-blue-600">
|
||||
Latest
|
||||
</Badge>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 w-[70px] shrink-0 cursor-pointer rounded-full text-xs"
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
restoreArtifact(artifact)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
>
|
||||
Restore
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
function ArtifactContentCopy() {
|
||||
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 1000 })
|
||||
|
||||
const { displayedArtifact } = useChatCanvas()
|
||||
if (!displayedArtifact) return null
|
||||
|
||||
const content = getArtifactContent(displayedArtifact)
|
||||
if (!content) return null
|
||||
|
||||
const handleCopy = () => {
|
||||
if (isCopied) return
|
||||
copyToClipboard(content)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="cursor-pointer rounded-full"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{isCopied ? <Check className="size-4" /> : <Copy className="size-4" />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function getArtifactContent(artifact: Artifact) {
|
||||
if (artifact.type === 'code') {
|
||||
return (artifact as CodeArtifact).data.code
|
||||
}
|
||||
|
||||
if (artifact.type === 'document') {
|
||||
return (artifact as DocumentArtifact).data.content
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function ArtifactDownloadButton() {
|
||||
const { displayedArtifact } = useChatCanvas()
|
||||
|
||||
if (!displayedArtifact) return null
|
||||
|
||||
const content = getArtifactContent(displayedArtifact)
|
||||
const fileName = getArtifactDownloadFileName(displayedArtifact)
|
||||
|
||||
if (!content || !fileName) return null
|
||||
|
||||
const handleDownload = () => {
|
||||
const blob = new Blob([content], { type: 'text/plain' })
|
||||
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="cursor-pointer rounded-full"
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<Download className="size-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function getArtifactDownloadFileName(artifact: Artifact) {
|
||||
if (artifact.type === 'code') {
|
||||
return (artifact as CodeArtifact).data.file_name
|
||||
}
|
||||
|
||||
if (artifact.type === 'document') {
|
||||
return (artifact as DocumentArtifact).data.title
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function CanvasCloseButton() {
|
||||
const { closeCanvas } = useChatCanvas()
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="cursor-pointer rounded-full"
|
||||
onClick={closeCanvas}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
ChatCanvasActions.History = ArtifactVersionHistory
|
||||
ChatCanvasActions.Copy = ArtifactContentCopy
|
||||
ChatCanvasActions.Download = ArtifactDownloadButton
|
||||
ChatCanvasActions.Close = CanvasCloseButton
|
||||
|
||||
export { ChatCanvasActions }
|
||||
@@ -0,0 +1,63 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from '../../../lib/utils'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../../../ui/tabs'
|
||||
import { CodeBlock } from '../../../widgets'
|
||||
import { CodeArtifact } from '../../annotation'
|
||||
import { ChatCanvasActions } from '../actions'
|
||||
import { useChatCanvas } from '../context'
|
||||
|
||||
interface CodeArtifactViewerProps {
|
||||
className?: string
|
||||
tabs?: Record<string, React.ReactNode>
|
||||
}
|
||||
|
||||
export function CodeArtifactViewer({
|
||||
className,
|
||||
tabs,
|
||||
}: CodeArtifactViewerProps) {
|
||||
const { displayedArtifact } = useChatCanvas()
|
||||
|
||||
if (displayedArtifact?.type !== 'code') return null
|
||||
|
||||
const {
|
||||
created_at,
|
||||
data: { language, code },
|
||||
} = displayedArtifact as CodeArtifact
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue="code"
|
||||
className={cn('flex h-full min-h-0 flex-1 flex-col gap-4 p-4', className)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<TabsList>
|
||||
<TabsTrigger value="code">Code</TabsTrigger>
|
||||
{tabs &&
|
||||
Object.entries(tabs).map(([key]) => (
|
||||
<TabsTrigger key={key} value={key} className="capitalize">
|
||||
{key}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<ChatCanvasActions />
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-auto pr-2">
|
||||
<TabsContent value="code" className="h-full">
|
||||
<CodeBlock
|
||||
key={created_at} // make the code block re-highlight when changing artifact
|
||||
language={language}
|
||||
value={code}
|
||||
showHeader={false}
|
||||
/>
|
||||
</TabsContent>
|
||||
{tabs &&
|
||||
Object.entries(tabs).map(([key, value]) => (
|
||||
<TabsContent key={key} value={key} className="h-full">
|
||||
{value}
|
||||
</TabsContent>
|
||||
))}
|
||||
</div>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
'use client'
|
||||
|
||||
import { FileText } from 'lucide-react'
|
||||
import { cn } from '../../../lib/utils'
|
||||
import { DocumentArtifact } from '../../annotation'
|
||||
import { ChatCanvasActions } from '../actions'
|
||||
import { useChatCanvas } from '../context'
|
||||
import { Markdown } from '../../../widgets'
|
||||
|
||||
interface DocumentArtifactViewerProps {
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export function DocumentArtifactViewer({
|
||||
className,
|
||||
children,
|
||||
}: DocumentArtifactViewerProps) {
|
||||
const { displayedArtifact } = useChatCanvas()
|
||||
|
||||
if (displayedArtifact?.type !== 'document') return null
|
||||
|
||||
const {
|
||||
data: { content, title, type },
|
||||
} = displayedArtifact as DocumentArtifact
|
||||
|
||||
return (
|
||||
<div className={cn('flex min-h-0 flex-1 flex-col', className)}>
|
||||
<div className="flex items-center justify-between border-b p-4">
|
||||
<h3 className="flex items-center gap-3 text-gray-600">
|
||||
<FileText className="size-8 text-blue-500" />
|
||||
<div className="flex flex-col">
|
||||
<div className="text font-semibold">{title}</div>
|
||||
<div className="text-xs text-gray-500">{type}</div>
|
||||
</div>
|
||||
</h3>
|
||||
<ChatCanvasActions />
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-1 flex-col items-stretch gap-4 overflow-auto px-20 py-4">
|
||||
{children ?? <Markdown content={content} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { FileCode, FileText, LucideIcon } from 'lucide-react'
|
||||
import { cn } from '../../lib/utils'
|
||||
import { Badge } from '../../ui/badge'
|
||||
import { Button } from '../../ui/button'
|
||||
import {
|
||||
Artifact,
|
||||
CodeArtifact,
|
||||
DocumentArtifact,
|
||||
isEqualArtifact,
|
||||
} from '../annotation'
|
||||
import { useChatCanvas } from './context'
|
||||
|
||||
const IconMap: Record<Artifact['type'], LucideIcon> = {
|
||||
code: FileCode,
|
||||
document: FileText,
|
||||
}
|
||||
|
||||
export function ArtifactCard({ artifact }: { artifact: Artifact }) {
|
||||
const {
|
||||
openArtifactInCanvas,
|
||||
getArtifactVersion,
|
||||
restoreArtifact,
|
||||
displayedArtifact,
|
||||
} = useChatCanvas()
|
||||
const { versionNumber, isLatest } = getArtifactVersion(artifact)
|
||||
|
||||
const Icon = IconMap[artifact.type]
|
||||
const title = getCardTitle(artifact)
|
||||
const isDisplayed =
|
||||
displayedArtifact && isEqualArtifact(artifact, displayedArtifact)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'border-border flex w-full max-w-72 cursor-pointer items-center justify-between gap-2 rounded-lg border-2 p-2 hover:border-blue-500',
|
||||
isDisplayed && 'border-blue-500'
|
||||
)}
|
||||
onClick={() => openArtifactInCanvas(artifact)}
|
||||
>
|
||||
<div className="flex flex-1 items-center gap-2">
|
||||
<Icon className="size-7 shrink-0 text-blue-500" />
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-semibold">Version {versionNumber}</div>
|
||||
{title && <div className="text-xs text-gray-600">{title}</div>}
|
||||
</div>
|
||||
</div>
|
||||
{isLatest ? (
|
||||
<Badge className="ml-2 bg-blue-500 hover:bg-blue-600">Latest</Badge>
|
||||
) : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 shrink-0 cursor-pointer text-xs"
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
restoreArtifact(artifact)
|
||||
}}
|
||||
>
|
||||
Restore
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const getCardTitle = (artifact: Artifact) => {
|
||||
if (artifact.type === 'code') {
|
||||
const { file_name } = artifact.data as CodeArtifact['data']
|
||||
return file_name
|
||||
}
|
||||
if (artifact.type === 'document') {
|
||||
const { title } = artifact.data as DocumentArtifact['data']
|
||||
return title
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
Artifact,
|
||||
CodeArtifact,
|
||||
extractArtifactsFromMessage,
|
||||
CodeArtifactError,
|
||||
extractArtifactsFromAllMessages,
|
||||
isEqualArtifact,
|
||||
} from '../annotation'
|
||||
import { Message } from '../chat.interface'
|
||||
import { useChatUI } from '../chat.context'
|
||||
|
||||
interface ChatCanvasContextType {
|
||||
allArtifacts: Artifact[]
|
||||
getArtifactsByType: (type: Artifact['type']) => Artifact[]
|
||||
displayedArtifact: Artifact | undefined
|
||||
isCanvasOpen: boolean
|
||||
openArtifactInCanvas: (artifact: Artifact) => void
|
||||
closeCanvas: () => void
|
||||
appendErrors: (artifact: CodeArtifact, errors: string[]) => void
|
||||
clearCodeErrors: (artifact: CodeArtifact) => void
|
||||
getCodeErrors: (artifact: CodeArtifact) => string[]
|
||||
fixCodeErrors: (artifact: CodeArtifact) => void
|
||||
getArtifactVersion: (artifact: Artifact) => {
|
||||
versionNumber: number
|
||||
isLatest: boolean
|
||||
}
|
||||
restoreArtifact: (artifact: Artifact) => void
|
||||
}
|
||||
|
||||
const ChatCanvasContext = createContext<ChatCanvasContextType | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
export function ChatCanvasProvider({ children }: { children: ReactNode }) {
|
||||
const { messages, isLoading, append, requestData, setMessages } = useChatUI()
|
||||
|
||||
const [isCanvasOpen, setIsCanvasOpen] = useState(false) // whether the canvas is open
|
||||
const [displayedArtifact, setDisplayedArtifact] = useState<Artifact>() // the artifact currently displayed in the canvas
|
||||
const [codeErrors, setCodeErrors] = useState<CodeArtifactError[]>([]) // contain all errors when compiling with Babel and runtime
|
||||
|
||||
const allArtifacts = useMemo(
|
||||
() => extractArtifactsFromAllMessages(messages),
|
||||
[messages]
|
||||
)
|
||||
|
||||
// get all artifacts from the last message, this may not be the latest artifact in case last message doesn't have any artifact
|
||||
const artifactsFromLastMessage = useMemo(() => {
|
||||
const lastMessage = messages[messages.length - 1]
|
||||
if (!lastMessage) return []
|
||||
const artifacts = extractArtifactsFromMessage(lastMessage)
|
||||
return artifacts
|
||||
}, [messages])
|
||||
|
||||
useEffect(() => {
|
||||
// when stream is loading and last message has a artifact, open the canvas with that artifact
|
||||
if (artifactsFromLastMessage.length > 0 && isLoading) {
|
||||
setIsCanvasOpen(true)
|
||||
setDisplayedArtifact(
|
||||
artifactsFromLastMessage[artifactsFromLastMessage.length - 1]
|
||||
)
|
||||
}
|
||||
}, [artifactsFromLastMessage, isCanvasOpen, isLoading])
|
||||
|
||||
const openArtifactInCanvas = (artifact: Artifact) => {
|
||||
setDisplayedArtifact(artifact)
|
||||
setIsCanvasOpen(true)
|
||||
}
|
||||
|
||||
const getArtifactsByType = (type: Artifact['type']) => {
|
||||
return allArtifacts.filter(a => a.type === type)
|
||||
}
|
||||
|
||||
const getArtifactVersion = (artifact: Artifact) => {
|
||||
const allArtifactsByCurrentType = getArtifactsByType(artifact.type)
|
||||
const versionNumber =
|
||||
allArtifactsByCurrentType.findIndex(a => isEqualArtifact(a, artifact)) + 1
|
||||
return {
|
||||
versionNumber,
|
||||
isLatest: versionNumber === allArtifactsByCurrentType.length,
|
||||
}
|
||||
}
|
||||
|
||||
const restoreArtifact = (artifact: Artifact) => {
|
||||
if (!setMessages) return
|
||||
|
||||
const newArtifact = {
|
||||
...artifact,
|
||||
created_at: Date.now(),
|
||||
}
|
||||
|
||||
const newMessages = [
|
||||
...messages,
|
||||
{
|
||||
id: `restore-msg-${Date.now()}`,
|
||||
role: 'user',
|
||||
content: `Restore to ${artifact.type} version ${getArtifactVersion(artifact).versionNumber}`,
|
||||
},
|
||||
{
|
||||
id: `restore-success-${Date.now()}`,
|
||||
role: 'assistant',
|
||||
content: `Successfully restored to ${artifact.type} version ${getArtifactVersion(artifact).versionNumber}`,
|
||||
annotations: [
|
||||
{
|
||||
type: 'artifact',
|
||||
data: newArtifact,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as (Message & { id: string })[]
|
||||
|
||||
setMessages(newMessages)
|
||||
|
||||
openArtifactInCanvas(newArtifact)
|
||||
}
|
||||
|
||||
const closeCanvas = () => {
|
||||
setIsCanvasOpen(false)
|
||||
setDisplayedArtifact(undefined)
|
||||
}
|
||||
|
||||
const appendErrors = (artifact: CodeArtifact, errors: string[]) => {
|
||||
setIsCanvasOpen(true)
|
||||
setCodeErrors(prev => [...prev, { artifact, errors }])
|
||||
}
|
||||
|
||||
const clearCodeErrors = (artifact: CodeArtifact) => {
|
||||
setCodeErrors(prev =>
|
||||
prev.filter(error => !isEqualArtifact(error.artifact, artifact))
|
||||
)
|
||||
}
|
||||
|
||||
const getCodeErrors = (artifact: CodeArtifact): string[] => {
|
||||
const artifactErrors = codeErrors.find(error =>
|
||||
isEqualArtifact(error.artifact, artifact)
|
||||
)
|
||||
const uniqueErrors = Array.from(new Set(artifactErrors?.errors ?? []))
|
||||
return uniqueErrors
|
||||
}
|
||||
|
||||
const fixCodeErrors = (artifact: CodeArtifact) => {
|
||||
const errors = getCodeErrors(artifact)
|
||||
if (errors.length === 0) return
|
||||
append(
|
||||
{
|
||||
role: 'user',
|
||||
content: `Please fix the following errors: ${errors.join('\n')} happened when running the code.`,
|
||||
},
|
||||
{ data: requestData }
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatCanvasContext.Provider
|
||||
value={{
|
||||
allArtifacts,
|
||||
getArtifactsByType,
|
||||
displayedArtifact,
|
||||
isCanvasOpen,
|
||||
openArtifactInCanvas,
|
||||
closeCanvas,
|
||||
appendErrors,
|
||||
clearCodeErrors,
|
||||
getCodeErrors,
|
||||
fixCodeErrors,
|
||||
getArtifactVersion,
|
||||
restoreArtifact,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ChatCanvasContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useChatCanvas(): ChatCanvasContextType {
|
||||
const context = useContext(ChatCanvasContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useChatCanvas must be used within a ChatCanvasProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from '../../lib/utils'
|
||||
import { ChatCanvasActions } from './actions'
|
||||
import { CodeArtifactViewer } from './artifacts/code'
|
||||
import { useChatCanvas } from './context'
|
||||
import { DocumentArtifactViewer } from './artifacts/document'
|
||||
|
||||
interface ChatCanvasProps {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
function ChatCanvas({ children, className }: ChatCanvasProps) {
|
||||
const { isCanvasOpen, displayedArtifact } = useChatCanvas()
|
||||
|
||||
if (!isCanvasOpen || !displayedArtifact) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'right-0 top-0 flex h-full w-3/5 shrink-0 flex-col border-l bg-white',
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
animation: isCanvasOpen
|
||||
? 'slideIn 0.3s ease-out forwards'
|
||||
: 'slideOut 0.3s ease-out forwards',
|
||||
}}
|
||||
>
|
||||
{children ?? (
|
||||
<>
|
||||
<CodeArtifactViewer />
|
||||
<DocumentArtifactViewer />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ChatCanvas.CodeArtifact = CodeArtifactViewer
|
||||
ChatCanvas.DocumentArtifact = DocumentArtifactViewer
|
||||
ChatCanvas.Actions = ChatCanvasActions
|
||||
|
||||
export default ChatCanvas
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from 'react'
|
||||
import {
|
||||
ChatAgentEvents,
|
||||
ChatEvents,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
AgentEventData,
|
||||
DocumentFileData,
|
||||
EventData,
|
||||
extractArtifactsFromMessage,
|
||||
getChatUIAnnotation,
|
||||
getSourceAnnotationData,
|
||||
ImageData,
|
||||
@@ -19,6 +21,7 @@ import {
|
||||
} from './annotation'
|
||||
import { useChatMessage } from './chat-message.context.js'
|
||||
import { useChatUI } from './chat.context.js'
|
||||
import { ArtifactCard } from './canvas/card.js'
|
||||
|
||||
export function EventAnnotations() {
|
||||
const { message, isLast, isLoading } = useChatMessage()
|
||||
@@ -112,3 +115,20 @@ export function SuggestedQuestionsAnnotations() {
|
||||
<SuggestedQuestions questions={suggestedQuestionsData[0]} append={append} />
|
||||
)
|
||||
}
|
||||
|
||||
export function ArtifactAnnotations() {
|
||||
const { message } = useChatMessage()
|
||||
const artifacts = useMemo(
|
||||
() => extractArtifactsFromMessage(message),
|
||||
[message]
|
||||
)
|
||||
if (!artifacts?.length) return null
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{artifacts.map((artifact, index) => (
|
||||
<ArtifactCard key={index} artifact={artifact} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CitationComponentProps, Markdown } from '../widgets/index.js'
|
||||
import { getSourceAnnotationData, MessageAnnotation } from './annotation'
|
||||
import {
|
||||
AgentEventAnnotations,
|
||||
ArtifactAnnotations,
|
||||
DocumentFileAnnotations,
|
||||
EventAnnotations,
|
||||
ImageAnnotations,
|
||||
@@ -119,6 +120,7 @@ function ChatMessageContent(props: ChatMessageContentProps) {
|
||||
<DocumentFileAnnotations />
|
||||
<SourceAnnotations />
|
||||
<SuggestedQuestionsAnnotations />
|
||||
<ArtifactAnnotations />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -204,6 +206,7 @@ type ComposibleChatMessageContent = typeof ChatMessageContent & {
|
||||
DocumentFile: typeof DocumentFileAnnotations
|
||||
Source: typeof SourceAnnotations
|
||||
SuggestedQuestions: typeof SuggestedQuestionsAnnotations
|
||||
Artifact: typeof ArtifactAnnotations
|
||||
}
|
||||
|
||||
type ComposibleChatMessage = typeof ChatMessage & {
|
||||
@@ -230,6 +233,7 @@ PrimiviteChatMessage.Content.Markdown = ChatMarkdown
|
||||
PrimiviteChatMessage.Content.DocumentFile = DocumentFileAnnotations
|
||||
PrimiviteChatMessage.Content.Source = SourceAnnotations
|
||||
PrimiviteChatMessage.Content.SuggestedQuestions = SuggestedQuestionsAnnotations
|
||||
PrimiviteChatMessage.Content.Artifact = ArtifactAnnotations
|
||||
|
||||
PrimiviteChatMessage.Avatar = ChatMessageAvatar
|
||||
PrimiviteChatMessage.Actions = ChatMessageActions
|
||||
|
||||
@@ -4,6 +4,7 @@ import ChatInput from './chat-input'
|
||||
import ChatMessages from './chat-messages'
|
||||
import { ChatProvider } from './chat.context'
|
||||
import { type ChatHandler } from './chat.interface'
|
||||
import { ChatCanvasProvider } from './canvas/context'
|
||||
|
||||
export interface ChatSectionProps extends React.PropsWithChildren {
|
||||
handler: ChatHandler
|
||||
@@ -24,7 +25,7 @@ export default function ChatSection(props: ChatSectionProps) {
|
||||
return (
|
||||
<ChatProvider value={{ ...handler, requestData, setRequestData }}>
|
||||
<div className={cn('flex h-full w-full flex-col gap-4 p-5', className)}>
|
||||
{children}
|
||||
<ChatCanvasProvider>{children}</ChatCanvasProvider>
|
||||
</div>
|
||||
</ChatProvider>
|
||||
)
|
||||
|
||||
@@ -27,6 +27,10 @@ export type ChatHandler = {
|
||||
message: Message,
|
||||
chatRequestOptions?: { data?: any }
|
||||
) => Promise<string | null | undefined>
|
||||
|
||||
// TODO: (Message & { id: string }) is a quick fix for compatibility with Message from ai/react
|
||||
// We should make Message type in ChatHandler more flexible. Eg: ChatHandler<T extends Message = Message>
|
||||
setMessages?: (messages: (Message & { id: string })[]) => void
|
||||
}
|
||||
|
||||
export type ChatContext = ChatHandler & {
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
export * from './chat/chat.interface'
|
||||
export * from './chat/annotation'
|
||||
export { default as ChatSection } from './chat/chat-section'
|
||||
export { default as ChatCanvas } from './chat/canvas'
|
||||
export { default as ChatInput } from './chat/chat-input'
|
||||
export { default as ChatMessages } from './chat/chat-messages'
|
||||
export { default as ChatMessage, ContentPosition } from './chat/chat-message'
|
||||
|
||||
// Context Provider Hooks
|
||||
export { useChatUI } from './chat/chat.context'
|
||||
export { useChatCanvas } from './chat/canvas/context'
|
||||
export { useChatMessage } from './chat/chat-message.context'
|
||||
export { useChatInput } from './chat/chat-input'
|
||||
export { useChatMessages } from './chat/chat-messages'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'highlight.js/styles/atom-one-dark-reasonable.css';
|
||||
@import 'highlight.js/styles/atom-one-light.css';
|
||||
@import 'katex/dist/katex.min.css';
|
||||
|
||||
.custom-markdown ul {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
'use client'
|
||||
|
||||
import * as AccordionPrimitive from '@radix-ui/react-accordion'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
const Accordion = AccordionPrimitive.Root
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn('border-b', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AccordionItem.displayName = 'AccordionItem'
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex flex-1 items-center justify-between py-4 text-left text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-neutral-500 transition-transform duration-200 dark:text-neutral-400" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('pb-4 pt-0', className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
))
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
|
||||
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
|
||||
secondary:
|
||||
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
|
||||
outline: 'text-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
@@ -0,0 +1,48 @@
|
||||
'use client'
|
||||
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
function Popover({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||
}
|
||||
|
||||
function PopoverTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
className,
|
||||
align = 'center',
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-slot="popover-content"
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-popover-content-transform-origin) outline-hidden z-50 w-72 rounded-md border p-4 shadow-md',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function PopoverAnchor({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger }
|
||||
@@ -3,11 +3,15 @@ import { Check, Copy, Download } from 'lucide-react'
|
||||
import { FC, memo, useEffect, useRef } from 'react'
|
||||
import { Button } from '../ui/button'
|
||||
import { useCopyToClipboard } from '../hook/use-copy-to-clipboard'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
interface Props {
|
||||
language: string
|
||||
value: string
|
||||
className?: string
|
||||
showHeader?: boolean
|
||||
headerClassName?: string
|
||||
codeClassName?: string
|
||||
}
|
||||
|
||||
interface languageMap {
|
||||
@@ -50,7 +54,15 @@ export const generateRandomString = (length: number, lowercase = false) => {
|
||||
return lowercase ? result.toLowerCase() : result
|
||||
}
|
||||
|
||||
const CodeBlock: FC<Props> = memo(({ language, value, className }) => {
|
||||
const CodeBlock: FC<Props> = memo(props => {
|
||||
const {
|
||||
language,
|
||||
value,
|
||||
className,
|
||||
headerClassName,
|
||||
codeClassName,
|
||||
showHeader = true,
|
||||
} = props
|
||||
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
|
||||
const codeRef = useRef<HTMLElement>(null)
|
||||
|
||||
@@ -94,28 +106,50 @@ const CodeBlock: FC<Props> = memo(({ language, value, className }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`codeblock relative w-full bg-zinc-950 font-sans ${className}`}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between bg-zinc-800 px-6 py-2 pr-4 text-zinc-100">
|
||||
<span className="text-xs lowercase">{language}</span>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Button variant="ghost" onClick={downloadAsFile} size="icon">
|
||||
<Download />
|
||||
<span className="sr-only">Download</span>
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" onClick={onCopy}>
|
||||
{isCopied ? (
|
||||
<Check className="h-4 w-4" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
<span className="sr-only">Copy code</span>
|
||||
</Button>
|
||||
<div className={cn('codeblock relative w-full', className)}>
|
||||
{showHeader && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute left-0 top-0 flex w-full items-center justify-between px-4 py-2',
|
||||
headerClassName
|
||||
)}
|
||||
>
|
||||
<span className="text-xs lowercase">{language}</span>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={downloadAsFile}
|
||||
size="icon"
|
||||
className="size-8"
|
||||
>
|
||||
<Download className="size-4" />
|
||||
<span className="sr-only">Download</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onCopy}
|
||||
className="size-8"
|
||||
>
|
||||
{isCopied ? (
|
||||
<Check className="size-4" />
|
||||
) : (
|
||||
<Copy className="size-4" />
|
||||
)}
|
||||
<span className="sr-only">Copy code</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="border border-zinc-700 text-white">
|
||||
<code ref={codeRef} className={`language-${language} font-mono`}>
|
||||
)}
|
||||
<pre>
|
||||
<code
|
||||
ref={codeRef}
|
||||
className={cn(
|
||||
`language-${language} rounded-lg border font-mono`,
|
||||
codeClassName,
|
||||
{ 'pt-12!': showHeader }
|
||||
)}
|
||||
>
|
||||
{value}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
Vendored
+1
@@ -70,6 +70,7 @@ module.exports = {
|
||||
'jsx-a11y/anchor-is-valid': 'off',
|
||||
'no-await-in-loop': 'off',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||
camelcase: 'off',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
||||
Generated
+449
@@ -108,6 +108,9 @@ importers:
|
||||
'@llamaindex/pdf-viewer':
|
||||
specifier: 1.3.0
|
||||
version: 1.3.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.3
|
||||
version: 1.2.8(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-collapsible':
|
||||
specifier: ^1.0.3
|
||||
version: 1.1.3(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
@@ -117,6 +120,9 @@ importers:
|
||||
'@radix-ui/react-icons':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.2(react@18.3.1)
|
||||
'@radix-ui/react-popover':
|
||||
specifier: ^1.1.7
|
||||
version: 1.1.11(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-progress':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.2(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
@@ -1455,6 +1461,37 @@ packages:
|
||||
resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
|
||||
dev: false
|
||||
|
||||
/@radix-ui/primitive@1.1.2:
|
||||
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-accordion@1.2.8(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-c7OKBvO36PfQIUGIjj1Wko0hH937pYFU2tR5zbIJDUsmTzHoZVHHt4bmb7OOJbzTaWJtVELKWojBHa7OcnUHmQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-collapsible': 1.1.8(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-collection': 1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-arrow@1.1.2(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==}
|
||||
peerDependencies:
|
||||
@@ -1474,6 +1511,25 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-arrow@1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-collapsible@1.1.3(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==}
|
||||
peerDependencies:
|
||||
@@ -1500,6 +1556,32 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-collapsible@1.1.8(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-hxEsLvK9WxIAPyxdDRULL4hcaSjMZCfP7fHB0Z1uUnDoDBat1Zh46hwYfa69DeZAbJrPckjf0AGAtEZyvDyJbw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-presence': 1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-collection@1.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==}
|
||||
peerDependencies:
|
||||
@@ -1523,6 +1605,28 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-collection@1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-slot': 1.2.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
|
||||
peerDependencies:
|
||||
@@ -1536,6 +1640,19 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-context@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
|
||||
peerDependencies:
|
||||
@@ -1549,6 +1666,19 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-context@1.1.2(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dialog@1.1.6(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==}
|
||||
peerDependencies:
|
||||
@@ -1594,6 +1724,19 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-direction@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dismissable-layer@1.1.5(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==}
|
||||
peerDependencies:
|
||||
@@ -1617,6 +1760,29 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dismissable-layer@1.1.7(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
|
||||
peerDependencies:
|
||||
@@ -1630,6 +1796,19 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-guards@1.1.2(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-scope@1.1.2(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==}
|
||||
peerDependencies:
|
||||
@@ -1651,6 +1830,27 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-scope@1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-hover-card@1.1.6(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ==}
|
||||
peerDependencies:
|
||||
@@ -1700,6 +1900,53 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-id@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popover@1.1.11(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-yFMfZkVA5G3GJnBgb2PxrrcLKm1ZLWXrbYVgdyTl//0TYEIHS9LJbnyz7WWcZ0qCq7hIlJZpRtxeSeIG5T5oJw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.7(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-focus-guards': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-focus-scope': 1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-popper': 1.2.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-portal': 1.1.6(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-presence': 1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-slot': 1.2.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
aria-hidden: 1.2.4
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-remove-scroll: 2.6.3(@types/react@18.3.18)(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popper@1.2.2(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==}
|
||||
peerDependencies:
|
||||
@@ -1728,6 +1975,34 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-popper@1.2.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.2(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-arrow': 1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/rect': 1.1.1
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-portal@1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==}
|
||||
peerDependencies:
|
||||
@@ -1748,6 +2023,26 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-portal@1.1.6(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
|
||||
peerDependencies:
|
||||
@@ -1769,6 +2064,26 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-presence@1.1.4(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-primitive@2.0.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==}
|
||||
peerDependencies:
|
||||
@@ -1789,6 +2104,25 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-primitive@2.1.0(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.0(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-progress@1.1.2(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==}
|
||||
peerDependencies:
|
||||
@@ -1890,6 +2224,20 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-slot@1.2.0(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-tabs@1.1.3(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==}
|
||||
peerDependencies:
|
||||
@@ -1930,6 +2278,19 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
|
||||
peerDependencies:
|
||||
@@ -1944,6 +2305,35 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.18)(react@18.3.1)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
|
||||
peerDependencies:
|
||||
@@ -1958,6 +2348,20 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
|
||||
peerDependencies:
|
||||
@@ -1971,6 +2375,19 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-previous@1.1.0(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==}
|
||||
peerDependencies:
|
||||
@@ -1998,6 +2415,20 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-rect@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/rect': 1.1.1
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-size@1.1.0(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
|
||||
peerDependencies:
|
||||
@@ -2012,6 +2443,20 @@ packages:
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-size@1.1.1(@types/react@18.3.18)(react@18.3.1):
|
||||
resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.18)(react@18.3.1)
|
||||
'@types/react': 18.3.18
|
||||
react: 18.3.1
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-visually-hidden@1.1.2(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1):
|
||||
resolution: {integrity: sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==}
|
||||
peerDependencies:
|
||||
@@ -2035,6 +2480,10 @@ packages:
|
||||
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
||||
dev: false
|
||||
|
||||
/@radix-ui/rect@1.1.1:
|
||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||
dev: false
|
||||
|
||||
/@rollup/rollup-android-arm-eabi@4.34.8:
|
||||
resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==}
|
||||
cpu: [arm]
|
||||
|
||||
Reference in New Issue
Block a user