Compare commits

...

10 Commits

Author SHA1 Message Date
Marcus Schiesser 9b8fd336ee Merge branch 'main' into feat/chat-ui-with-rsc-example 2024-11-15 11:51:47 +08:00
Marcus Schiesser 13731fa502 add docs 2024-11-15 11:49:35 +08:00
Thuc Pham 6f9aa60bec feat: mock readable stream 2024-11-15 10:44:24 +07:00
Thuc Pham c8e0d84ad8 fix: remove unneccesary change 2024-11-15 09:36:42 +07:00
Thuc Pham c8613301b0 specifiy use server and add doc step 2024-11-15 09:35:33 +07:00
Thuc Pham 3e6dfc9ab6 docs: add chat RSC demo to doc 2024-11-14 17:07:18 +07:00
Thuc Pham 6f72e899e3 add rsc example 2024-11-14 16:54:38 +07:00
Thuc Pham 082a03849a use 0.0.7 chat-ui 2024-11-14 16:50:12 +07:00
Thuc Pham 82308917cd refactor: move example to chat folder 2024-11-14 16:46:00 +07:00
Thuc Pham 77a7404a95 update to latest chat-ui 2024-11-14 16:45:38 +07:00
12 changed files with 306 additions and 45 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
},
"dependencies": {
"@icons-pack/react-simple-icons": "^10.1.0",
"@llamaindex/chat-ui": "0.0.5",
"@llamaindex/chat-ui": "0.0.8",
"@llamaindex/cloud": "workspace:*",
"@llamaindex/core": "workspace:*",
"@llamaindex/node-parser": "workspace:*",
+16 -13
View File
@@ -1,6 +1,6 @@
import { LlamaIndexAdapter, type Message } from "ai";
import { SimpleChatEngine, type ChatMessage } from "llamaindex";
import { NextResponse, type NextRequest } from "next/server";
import { Message } from "ai";
import { simulateReadableStream } from "ai/test";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
try {
@@ -12,16 +12,19 @@ export async function POST(request: NextRequest) {
{ status: 400 },
);
}
const chatEngine = new SimpleChatEngine();
return LlamaIndexAdapter.toDataStreamResponse(
await chatEngine.chat({
message: userMessage.content,
chatHistory: messages as ChatMessage[],
stream: true,
}),
{},
const mockResponse = `Hello! This is a mock response to: ${userMessage.content}`;
return new Response(
simulateReadableStream({
chunkDelayInMs: 20,
values: mockResponse.split(" ").map((t) => `0:"${t} "\n`),
}).pipeThrough(new TextEncoderStream()),
{
status: 200,
headers: {
"X-Vercel-AI-Data-Stream": "v1",
"Content-Type": "text/plain; charset=utf-8",
},
},
);
} catch (error) {
const detail = (error as Error).message;
-8
View File
@@ -1,8 +0,0 @@
"use client";
import { ChatSection } from "@llamaindex/chat-ui";
import { useChat } from "ai/react";
export const ChatDemo = () => {
const handler = useChat();
return <ChatSection handler={handler} />;
};
@@ -0,0 +1,16 @@
"use client";
import { ChatInput, ChatMessages, ChatSection } from "@llamaindex/chat-ui";
import { useChat } from "ai/react";
export const ChatDemo = () => {
const handler = useChat();
return (
<ChatSection handler={handler}>
<ChatMessages>
<ChatMessages.List className="h-auto max-h-[400px]" />
<ChatMessages.Actions />
</ChatMessages>
<ChatInput />
</ChatSection>
);
};
@@ -0,0 +1,55 @@
import { Markdown } from "@llamaindex/chat-ui/widgets";
import { generateId, Message, parseStreamPart } from "ai";
import { createAI, createStreamableUI, getMutableAIState } from "ai/rsc";
import { simulateReadableStream } from "ai/test";
import { ReactNode } from "react";
type ServerState = Message[];
type FrontendState = Array<Message & { display: ReactNode }>;
type Actions = {
chat: (message: Message) => Promise<Message & { display: ReactNode }>;
};
export const AI = createAI<ServerState, FrontendState, Actions>({
initialAIState: [],
initialUIState: [],
actions: {
chat: async (message: Message) => {
"use server";
const aiState = getMutableAIState<typeof AI>();
aiState.update((prev) => [...prev, message]);
const mockResponse = `Hello! This is a mock response to: ${message.content}`;
const responseStream = simulateReadableStream({
chunkDelayInMs: 20,
values: mockResponse.split(" ").map((t) => `0:"${t} "\n`),
});
const uiStream = createStreamableUI();
const assistantMessage: Message = {
id: generateId(),
role: "assistant",
content: "",
};
responseStream.pipeTo(
new WritableStream({
write: async (message) => {
assistantMessage.content += parseStreamPart(message).value;
uiStream.update(<Markdown content={assistantMessage.content} />);
},
close: () => {
aiState.done([...aiState.get(), assistantMessage]);
uiStream.done();
},
}),
);
return {
...assistantMessage,
display: uiStream.value,
};
},
},
});
@@ -0,0 +1,33 @@
"use client";
import {
ChatInput,
ChatMessage,
ChatMessages,
ChatSection as ChatSectionUI,
} from "@llamaindex/chat-ui";
import { useChatRSC } from "./use-chat-rsc";
export const ChatSectionRSC = () => {
const handler = useChatRSC();
return (
<ChatSectionUI handler={handler}>
<ChatMessages>
<ChatMessages.List className="h-auto max-h-[400px]">
{handler.messages.map((message, index) => (
<ChatMessage
key={index}
message={message}
isLast={index === handler.messages.length - 1}
>
<ChatMessage.Avatar />
<ChatMessage.Content>{message.display}</ChatMessage.Content>
</ChatMessage>
))}
<ChatMessages.Loading />
</ChatMessages.List>
</ChatMessages>
<ChatInput />
</ChatSectionUI>
);
};
@@ -0,0 +1,8 @@
import { AI } from "./ai-action";
import { ChatSectionRSC } from "./chat-section";
export const ChatDemoRSC = () => (
<AI>
<ChatSectionRSC />
</AI>
);
@@ -0,0 +1,41 @@
"use client";
import { useActions } from "ai/rsc";
import { generateId, Message } from "ai";
import { useUIState } from "ai/rsc";
import { useState } from "react";
import { AI } from "./ai-action";
export function useChatRSC() {
const [input, setInput] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [messages, setMessages] = useUIState<typeof AI>();
const { chat } = useActions<typeof AI>();
const append = async (message: Omit<Message, "id">) => {
const newMsg: Message = { ...message, id: generateId() };
setIsLoading(true);
try {
setMessages((prev) => [...prev, { ...newMsg, display: message.content }]);
const assistantMsg = await chat(newMsg);
setMessages((prev) => [...prev, assistantMsg]);
} catch (error) {
console.error(error);
}
setIsLoading(false);
setInput("");
return message.content;
};
return {
input,
setInput,
isLoading,
messages,
setMessages,
append,
};
}
@@ -1,8 +1,8 @@
---
title: Chat-UI
description: Use chat-ui to add a chat interface to your LlamaIndexTS application.
title: Using API Route
description: Chat interface for your LlamaIndexTS application using API Route
---
import { ChatDemo } from '../../../../components/demo/chat';
import { ChatDemo } from '../../../../../components/demo/chat/api/demo';
import "@llamaindex/chat-ui/styles/code.css";
import "@llamaindex/chat-ui/styles/katex.css";
@@ -26,7 +26,7 @@ This is the simplest way to add a chat interface to your application. Copy the f
```json doc-gen:file
{
"file": "./src/components/demo/chat.tsx",
"file": "./src/components/demo/chat/api/demo.tsx",
"codeblock": true
}
```
@@ -37,6 +37,7 @@ Combining both, you're getting a fully functional chat interface:
<ChatDemo />
## Next Steps
The steps above are the bare minimum to get a chat interface working. From here, you can go two ways:
@@ -0,0 +1,6 @@
{
"title": "Chat-UI",
"description": "Use chat-ui to add a chat interface to your LlamaIndexTS application.",
"defaultOpen": false,
"pages": ["chat", "rsc"]
}
@@ -0,0 +1,68 @@
---
title: Using Next.js RSC
description: Chat interface for your LlamaIndexTS application using Next.js RSC
---
import { ChatDemoRSC } from '../../../../../components/demo/chat/rsc/demo';
import "@llamaindex/chat-ui/styles/code.css";
import "@llamaindex/chat-ui/styles/katex.css";
Using [chat-ui](https://github.com/run-llama/chat-ui), it's easy to add a chat interface to your LlamaIndexTS application using [Next.js RSC](https://nextjs.org/docs/app/building-your-application/rendering/server-components) and [Vercel AI RSC](https://sdk.vercel.ai/docs/ai-sdk-rsc/overview).
With RSC, the chat messages are not returned as JSON from the server (like when using an [API route](./chat)), instead the chat message components are rendered on the server side.
This is for example useful for rendering a whole chat history on the server before sending it to the client. [Check here](https://sdk.vercel.ai/docs/getting-started/navigating-the-library#when-to-use-ai-sdk-rsc), for a discussion of when to use use RSC.
For implementing a chat interface with RSC, you need to create an AI action and then connect the chat interface to use it.
## Create an AI action
First, define an [AI context provider](https://sdk.vercel.ai/examples/rsc/state-management/ai-ui-states) with a chat server action:
```json doc-gen:file
{
"file": "./src/components/demo/chat/rsc/ai-action.tsx",
"codeblock": true
}
```
The chat server action is using LlamaIndexTS to generate a response based on the chat history and the user input.
## Create the chat UI
The entrypoint of our application initializes the AI provider for the application and adds a `ChatSection` component:
```json doc-gen:file
{
"file": "./src/components/demo/chat/rsc/demo.tsx",
"codeblock": true
}
```
The `ChatSection` component is created by using chat components from @llamaindex/chat-ui:
```json doc-gen:file
{
"file": "./src/components/demo/chat/rsc/chat-section.tsx",
"codeblock": true
}
```
It is using a `useChatRSC` hook to conntect the chat interface to the `chat` AI action that we defined earlier:
```json doc-gen:file
{
"file": "./src/components/demo/chat/rsc/use-chat-rsc.tsx",
"codeblock": true
}
```
## Try RSC Chat ⬇️
<ChatDemoRSC />
## Next Steps
The steps above are the bare minimum to get a chat interface working with RSC. From here, you can go two ways:
1. Use our [full-stack RSC example](https://github.com/run-llama/nextjs-rsc) based on [create-llama](https://github.com/run-llama/create-llama) to get started quickly with a fully working chat interface or
2. Learn more about [AI RSC](https://sdk.vercel.ai/examples/rsc), [chat-ui](https://github.com/run-llama/chat-ui) and [LlamaIndexTS](https://github.com/run-llama/llamaindex-ts) to customize the chat interface and AI actions to your needs.
+57 -19
View File
@@ -127,8 +127,8 @@ importers:
specifier: ^10.1.0
version: 10.1.0(react@18.3.1)
'@llamaindex/chat-ui':
specifier: 0.0.5
version: 0.0.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: 0.0.8
version: 0.0.8(@types/react-dom@18.3.1)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@llamaindex/cloud':
specifier: workspace:*
version: link:../../packages/cloud
@@ -173,10 +173,10 @@ importers:
version: 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@vercel/functions':
specifier: ^1.5.0
version: 1.5.0(@aws-sdk/credential-provider-web-identity@3.679.0(@aws-sdk/client-sts@3.682.0))
version: 1.5.0(@aws-sdk/credential-provider-web-identity@3.679.0)
ai:
specifier: ^3.4.31
version: 3.4.31(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.1.9))(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8)
version: 3.4.31(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8)
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@@ -257,7 +257,7 @@ importers:
version: 1.22.2
shiki-magic-move:
specifier: ^0.5.0
version: 0.5.0(react@18.3.1)(shiki@1.22.2)(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3))
version: 0.5.0(react@18.3.1)(shiki@1.22.2)(vue@3.5.12(typescript@5.6.3))
swr:
specifier: ^2.2.5
version: 2.2.5(react@18.3.1)
@@ -428,7 +428,7 @@ importers:
dependencies:
ai:
specifier: ^3.3.21
version: 3.4.31(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.1.9))(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8)
version: 3.4.31(openai@4.69.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.1.9))(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8)
llamaindex:
specifier: workspace:*
version: link:../../../packages/llamaindex
@@ -606,7 +606,7 @@ importers:
version: 2.4.9
chromadb:
specifier: ^1.8.1
version: 1.9.2(cohere-ai@7.14.0(encoding@0.1.13))(encoding@0.1.13)(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))
version: 1.9.2(cohere-ai@7.14.0(encoding@0.1.13))(encoding@0.1.13)(openai@4.69.0(encoding@0.1.13))
commander:
specifier: ^12.1.0
version: 12.1.0
@@ -738,7 +738,7 @@ importers:
version: 1.1.0(@types/react@18.3.12)(react@18.3.1)
ai:
specifier: ^3.3.21
version: 3.4.31(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.1.9))(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8)
version: 3.4.31(openai@4.69.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.1.9))(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8)
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@@ -3755,8 +3755,8 @@ packages:
'@leichtgewicht/ip-codec@2.0.5':
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
'@llamaindex/chat-ui@0.0.5':
resolution: {integrity: sha512-nGlhtAeIlVBtHAsCtBN5f+/iJpIHQ7uFf43ebye/wATf5tT12HUl3tP5O/+XC7nyXXwByjk5KAEN+hk3aC1Cug==}
'@llamaindex/chat-ui@0.0.8':
resolution: {integrity: sha512-yTWgxDzJD6F8w17CNIIJkSBE+hyd+WRqhF6lSElqo3k7CQyhtYr8cOD7cS6K8gBqaI3Xha5EPqvqV0+enJA0Mg==}
peerDependencies:
react: ^18.2.0
@@ -16534,7 +16534,7 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.5': {}
'@llamaindex/chat-ui@0.0.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@llamaindex/chat-ui@0.0.8(@types/react-dom@18.3.1)(@types/react@18.3.12)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@llamaindex/pdf-viewer': 1.2.0(@types/react@18.3.12)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-collapsible': 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -18616,7 +18616,7 @@ snapshots:
'@upstash/vector@1.1.7': {}
'@vercel/functions@1.5.0(@aws-sdk/credential-provider-web-identity@3.679.0(@aws-sdk/client-sts@3.682.0))':
'@vercel/functions@1.5.0(@aws-sdk/credential-provider-web-identity@3.679.0)':
optionalDependencies:
'@aws-sdk/credential-provider-web-identity': 3.679.0(@aws-sdk/client-sts@3.682.0)
@@ -18894,7 +18894,7 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
ai@3.4.31(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.1.9))(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8):
ai@3.4.31(openai@4.69.0(encoding@0.1.13)(zod@3.23.8))(react@18.3.1)(sswr@2.1.0)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8):
dependencies:
'@ai-sdk/provider': 0.0.26
'@ai-sdk/provider-utils': 1.0.22(zod@3.23.8)
@@ -18913,6 +18913,30 @@ snapshots:
openai: 4.69.0(encoding@0.1.13)(zod@3.23.8)
react: 18.3.1
sswr: 2.1.0(svelte@5.1.9)
zod: 3.23.8
transitivePeerDependencies:
- solid-js
- vue
ai@3.4.31(openai@4.69.0(zod@3.23.8))(react@18.3.1)(sswr@2.1.0(svelte@5.1.9))(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3))(zod@3.23.8):
dependencies:
'@ai-sdk/provider': 0.0.26
'@ai-sdk/provider-utils': 1.0.22(zod@3.23.8)
'@ai-sdk/react': 0.0.70(react@18.3.1)(zod@3.23.8)
'@ai-sdk/solid': 0.0.54(zod@3.23.8)
'@ai-sdk/svelte': 0.0.57(svelte@5.1.9)(zod@3.23.8)
'@ai-sdk/ui-utils': 0.0.50(zod@3.23.8)
'@ai-sdk/vue': 0.0.59(vue@3.5.12(typescript@5.6.3))(zod@3.23.8)
'@opentelemetry/api': 1.9.0
eventsource-parser: 1.1.2
json-schema: 0.4.0
jsondiffpatch: 0.6.0
secure-json-parse: 2.7.0
zod-to-json-schema: 3.23.5(zod@3.23.8)
optionalDependencies:
openai: 4.69.0(zod@3.23.8)
react: 18.3.1
sswr: 2.1.0(svelte@5.1.9)
svelte: 5.1.9
zod: 3.23.8
transitivePeerDependencies:
@@ -19600,7 +19624,7 @@ snapshots:
transitivePeerDependencies:
- encoding
chromadb@1.9.2(cohere-ai@7.14.0(encoding@0.1.13))(encoding@0.1.13)(openai@4.69.0(encoding@0.1.13)(zod@3.23.8)):
chromadb@1.9.2(cohere-ai@7.14.0(encoding@0.1.13))(encoding@0.1.13)(openai@4.69.0(encoding@0.1.13)):
dependencies:
cliui: 8.0.1
isomorphic-fetch: 3.0.0(encoding@0.1.13)
@@ -20687,7 +20711,7 @@ snapshots:
debug: 4.3.7
enhanced-resolve: 5.17.1
eslint: 9.14.0(jiti@2.4.0)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0))
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0))
fast-glob: 3.3.2
get-tsconfig: 4.8.1
is-bun-module: 1.1.0
@@ -20700,7 +20724,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0)):
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -20722,7 +20746,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.14.0(jiti@2.4.0)
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.14.0(jiti@2.4.0))
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@2.4.0))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.14.0(jiti@2.4.0)))(eslint@9.14.0(jiti@2.4.0))
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -24388,6 +24412,21 @@ snapshots:
transitivePeerDependencies:
- encoding
openai@4.69.0(zod@3.23.8):
dependencies:
'@types/node': 18.19.64
'@types/node-fetch': 2.6.11
abort-controller: 3.0.0
agentkeepalive: 4.5.0
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0(encoding@0.1.13)
optionalDependencies:
zod: 3.23.8
transitivePeerDependencies:
- encoding
optional: true
openapi-sampler@1.5.1:
dependencies:
'@types/json-schema': 7.0.15
@@ -26259,14 +26298,13 @@ snapshots:
interpret: 1.4.0
rechoir: 0.6.2
shiki-magic-move@0.5.0(react@18.3.1)(shiki@1.22.2)(svelte@5.1.9)(vue@3.5.12(typescript@5.6.3)):
shiki-magic-move@0.5.0(react@18.3.1)(shiki@1.22.2)(vue@3.5.12(typescript@5.6.3)):
dependencies:
diff-match-patch-es: 0.1.1
ohash: 1.1.4
optionalDependencies:
react: 18.3.1
shiki: 1.22.2
svelte: 5.1.9
vue: 3.5.12(typescript@5.6.3)
shiki@1.22.2: