Skip to content

Commit c237ff2

Browse files
Handle already connected wallets in 1193 provider
1 parent bdcbe0e commit c237ff2

File tree

30 files changed

+3373
-193
lines changed

30 files changed

+3373
-193
lines changed

.changeset/many-pants-tease.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Handle already connected wallets in 1193 provider

apps/playground-web/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{
22
"dependencies": {
33
"@abstract-foundation/agw-react": "^1.6.4",
4+
"@ai-sdk/react": "^2.0.25",
45
"@hookform/resolvers": "^3.9.1",
6+
"@radix-ui/react-avatar": "^1.1.10",
57
"@radix-ui/react-checkbox": "^1.3.2",
68
"@radix-ui/react-collapsible": "^1.1.11",
79
"@radix-ui/react-dialog": "1.1.14",
@@ -14,8 +16,11 @@
1416
"@radix-ui/react-slot": "^1.2.3",
1517
"@radix-ui/react-switch": "^1.2.5",
1618
"@radix-ui/react-tooltip": "1.2.7",
19+
"@radix-ui/react-use-controllable-state": "^1.2.2",
1720
"@tanstack/react-query": "5.81.5",
21+
"@thirdweb-dev/ai-sdk-provider": "workspace:*",
1822
"@workspace/ui": "workspace:*",
23+
"ai": "^5.0.25",
1924
"class-variance-authority": "^0.7.1",
2025
"clsx": "^2.1.1",
2126
"date-fns": "4.1.0",
@@ -37,9 +42,11 @@
3742
"server-only": "^0.0.1",
3843
"shiki": "1.27.0",
3944
"sonner": "2.0.6",
45+
"streamdown": "^1.1.4",
4046
"tailwind-merge": "^2.6.0",
4147
"thirdweb": "workspace:*",
4248
"use-debounce": "^10.0.5",
49+
"use-stick-to-bottom": "^1.1.1",
4350
"zod": "3.25.75"
4451
},
4552
"devDependencies": {
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
"use client";
2+
3+
import { useChat } from "@ai-sdk/react";
4+
import { DefaultChatTransport } from "ai";
5+
import { useMemo, useState } from "react";
6+
import { defineChain, prepareTransaction } from "thirdweb";
7+
import {
8+
ConnectButton,
9+
TransactionButton,
10+
useActiveAccount,
11+
} from "thirdweb/react";
12+
import { THIRDWEB_CLIENT } from "../../../../lib/client";
13+
import { ThirdwebAiMessage } from "@thirdweb-dev/ai-sdk-provider";
14+
import { Response } from "@/components/response";
15+
import {
16+
Conversation,
17+
ConversationContent,
18+
ConversationScrollButton,
19+
} from "@/components/conversation";
20+
import { Message, MessageContent } from "@/components/message";
21+
import {
22+
Reasoning,
23+
ReasoningContent,
24+
ReasoningTrigger,
25+
} from "@/components/reasoning";
26+
import { PromptInput, PromptInputSubmit, PromptInputTextarea, PromptInputToolbar } from "@/components/prompt-input";
27+
28+
export function ChatContainer() {
29+
const [sessionId, setSessionId] = useState("");
30+
31+
const { messages, sendMessage, status, setMessages,addToolResult } =
32+
useChat<ThirdwebAiMessage>({
33+
transport: new DefaultChatTransport({
34+
api: "/api/chat",
35+
}),
36+
onFinish: ({ message }) => {
37+
setSessionId(message.metadata?.session_id ?? "");
38+
},
39+
});
40+
const [input, setInput] = useState("");
41+
42+
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
43+
e.preventDefault();
44+
if (input.trim()) {
45+
sendMessage(
46+
{ text: input },
47+
{
48+
body: {
49+
sessionId,
50+
},
51+
}
52+
);
53+
setInput("");
54+
}
55+
};
56+
57+
return (
58+
<div className="max-w-4xl mx-auto p-6 relative size-full">
59+
<div className="flex flex-col h-[600px] rounded-lg border">
60+
<Conversation>
61+
<ConversationContent>
62+
{messages.map((message) => (
63+
<Message from={message.role} key={message.id}>
64+
<MessageContent>
65+
{message.parts.map((part, i) => {
66+
switch (part.type) {
67+
case "text": // we don't use any reasoning or tool calls in this example
68+
return (
69+
<Response key={`${message.id}-${i}`}>
70+
{part.text}
71+
</Response>
72+
);
73+
case "reasoning":
74+
return (
75+
<Reasoning
76+
key={`${message.id}-${i}`}
77+
className="w-full"
78+
isStreaming={status === "streaming"}
79+
>
80+
<ReasoningTrigger />
81+
<ReasoningContent className="text-xs text-muted-foreground italic flex flex-col gap-2">{part.text}</ReasoningContent>
82+
</Reasoning>
83+
);
84+
case "tool-sign_transaction":
85+
return (
86+
<SignTransactionButton input={part.input} addToolResult={addToolResult} toolCallId={part.toolCallId} />
87+
);
88+
default:
89+
return null;
90+
}
91+
})}
92+
</MessageContent>
93+
</Message>
94+
))}
95+
</ConversationContent>
96+
<ConversationScrollButton />
97+
</Conversation>
98+
99+
<PromptInput
100+
onSubmit={handleSubmit}
101+
className="mt-4 w-full max-w-2xl mx-auto relative"
102+
>
103+
<PromptInputTextarea
104+
value={input}
105+
placeholder="Say something..."
106+
onChange={(e) => setInput(e.currentTarget.value)}
107+
className="pr-12"
108+
/>
109+
<PromptInputSubmit
110+
status={status === 'streaming' ? 'streaming' : 'ready'}
111+
disabled={!input.trim()}
112+
className="absolute top-4 right-4"
113+
/>
114+
</PromptInput>
115+
116+
</div>
117+
</div>
118+
);
119+
120+
// return (
121+
// <>
122+
// {messages.map((message) => (
123+
// <div
124+
// key={message.id}
125+
// style={{
126+
// marginBottom: "1rem",
127+
// padding: "0.5rem",
128+
// border: "1px solid #ccc",
129+
// }}
130+
// >
131+
// <strong>{message.role === "user" ? "User: " : "AI: "}</strong>
132+
// <div>
133+
// {message.parts.map((part, index) => {
134+
// switch (part.type) {
135+
// case "text":
136+
// return (
137+
// <div key={index}>
138+
// <span>{part.text}</span>
139+
// </div>
140+
// );
141+
// case "reasoning":
142+
// return (
143+
// <div
144+
// key={index}
145+
// style={{ fontStyle: "italic", color: "#666" }}
146+
// >
147+
// 💭 {part.text}
148+
// </div>
149+
// );
150+
// default: {
151+
// // Handle tool-call and tool-result parts
152+
// if (part.type.startsWith("tool-")) {
153+
// console.log("---part", part);
154+
// switch (part.type) {
155+
// case "tool-sign_transaction": {
156+
// const input = part.input;
157+
// console.log("---input", input);
158+
// if (!input) {
159+
// return null;
160+
// }
161+
// return (
162+
// <div
163+
// key={index}
164+
// className="flex flex-col gap-2"
165+
// >
166+
// <strong>Sign Transaction:</strong> {part.type}
167+
// <SignTransactionButton input={input} addToolResult={addToolResult} toolCallId={part.toolCallId} />
168+
// </div>
169+
// );
170+
// }
171+
// }
172+
// }
173+
// return null;
174+
// }
175+
// }
176+
// })}
177+
// </div>
178+
// </div>
179+
// ))}
180+
181+
// <form
182+
// onSubmit={(e) => {
183+
// e.preventDefault();
184+
// send(input)
185+
// }}
186+
// >
187+
// <input
188+
// value={input}
189+
// onChange={(e) => setInput(e.target.value)}
190+
// disabled={status !== "ready"}
191+
// placeholder="Say something..."
192+
// />
193+
// <button type="submit" disabled={status !== "ready"}>
194+
// Submit
195+
// </button>
196+
// </form>
197+
// </>
198+
// );
199+
}
200+
201+
type SignTransactionButtonProps = {
202+
input: {
203+
chain_id?: number;
204+
to?: string;
205+
data?: string;
206+
value?: string;
207+
} | undefined;
208+
addToolResult: ReturnType<typeof useChat<ThirdwebAiMessage>>["addToolResult"];
209+
toolCallId: string;
210+
};
211+
212+
const SignTransactionButton = (props: SignTransactionButtonProps) => {
213+
const { input, addToolResult, toolCallId } = props;
214+
const transactionData: {
215+
chain_id: number;
216+
to: string;
217+
data: `0x${string}`;
218+
value: bigint;
219+
} = useMemo(() => {
220+
return {
221+
chain_id: input?.chain_id || 8453,
222+
to: input?.to || "",
223+
data: (input?.data as `0x${string}`) || "0x",
224+
value: input?.value ? BigInt(input.value) : BigInt(0),
225+
};
226+
}, [input]);
227+
const account = useActiveAccount();
228+
229+
if (!account) {
230+
return <ConnectButton client={THIRDWEB_CLIENT} />;
231+
}
232+
233+
return (
234+
<div className="py-4">
235+
<TransactionButton
236+
style={{
237+
width: "100%",
238+
}}
239+
transaction={() =>
240+
prepareTransaction({
241+
client: THIRDWEB_CLIENT,
242+
chain: defineChain(transactionData.chain_id),
243+
to: transactionData.to,
244+
data: transactionData.data,
245+
value: transactionData.value,
246+
})
247+
}
248+
onTransactionSent={(transaction) => {
249+
addToolResult({
250+
tool: "sign_transaction",
251+
toolCallId,
252+
output: {
253+
transaction_hash: transaction.transactionHash,
254+
},
255+
});
256+
}}
257+
>
258+
Sign Transaction
259+
</TransactionButton>
260+
</div>
261+
);
262+
};

0 commit comments

Comments
 (0)