-
Notifications
You must be signed in to change notification settings - Fork 682
[Portal] Restore Ask AI #8923
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Portal] Restore Ask AI #8923
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,23 +1,60 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use server"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const serviceKey = process.env.SIWA_SERVICE_KEY as string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const apiUrl = process.env.SIWA_URL; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { headers } from "next/headers"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const secretKey = process.env.THIRDWEB_AI_SECRET_KEY as string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const apiUrl = process.env.THIRDWEB_AI_URL || "https://api.thirdweb.com/ai"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const clientId = process.env.THIRDWEB_AI_CLIENT_ID as string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const WINDOW_MS = 60_000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const MAX_PER_WINDOW = 10; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hits = new Map<string, number[]>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function withinRateLimit() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const key = (await headers()).get("x-forwarded-for")?.split(",")[0]?.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!key) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const now = Date.now(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const recent = (hits.get(key) ?? []).filter((t) => now - t < WINDOW_MS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| recent.push(now); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hits.set(key, recent); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Do not retain rejected attempts. The code appends timestamps before it checks Proposed fix const now = Date.now();
const recent = (hits.get(key) ?? []).filter((t) => now - t < WINDOW_MS);
+if (recent.length >= MAX_PER_WINDOW) {
+ hits.set(key, recent);
+ return false;
+}
+
recent.push(now);
hits.set(key, recent);
-
-return recent.length <= MAX_PER_WINDOW;
+return true;🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (hits.size > 10_000) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const [k, v] of hits) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (v.every((t) => now - t >= WINDOW_MS)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hits.delete(k); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return recent.length <= MAX_PER_WINDOW; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+32
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rate limiting logic has a critical bug: the current request timestamp is added to the Impact: If a user hits the rate limit, their rejected request timestamps are stored, making it harder for them to make successful requests later. For example, if they make 15 requests rapidly (exceeding the 10 request limit), all 15 timestamps are stored, meaning they'll be rate limited for the full 60 seconds even though only the first 10 should count. Fix: const now = Date.now();
const recent = (hits.get(key) ?? []).filter((t) => now - t < WINDOW_MS);
// Check limit BEFORE adding current request
if (recent.length >= MAX_PER_WINDOW) {
return false;
}
recent.push(now);
hits.set(key, recent);
if (hits.size > 10_000) {
for (const [k, v] of hits) {
if (v.every((t) => now - t >= WINDOW_MS)) {
hits.delete(k);
}
}
}
return true;
Suggested change
Spotted by Graphite |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const getChatResponse = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userMessage: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sessionId: string | undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const payload = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversationId: sessionId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: userMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source: "portal", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${apiUrl}/v1/chat`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify(payload), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!(await withinRateLimit())) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversationId: sessionId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: "You're sending messages too quickly. Please wait a moment and try again.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestId: undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${apiUrl}/chat`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| context: { session_id: sessionId }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| messages: [{ content: userMessage, role: "user" }], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stream: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "x-service-api-key": serviceKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "x-client-id": clientId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "x-secret-key": secretKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -30,10 +67,16 @@ export const getChatResponse = async ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const data = (await response.json()) as { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversationId: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| session_id: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request_id: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversationId: data.session_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: data.message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestId: data.request_id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Chat API error:", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,14 +88,20 @@ export const getChatResponse = async ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const sendFeedback = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| conversationId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| feedbackRating: 1 | -1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${apiUrl}/v1/chat/feedback`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify({ conversationId, feedbackRating }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${apiUrl}/feedback`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| feedback_rating: feedbackRating, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request_id: requestId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| session_id: conversationId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "x-service-api-key": serviceKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "x-client-id": clientId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "x-secret-key": secretKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ interface Message { | |
| content: string; | ||
| isLoading?: boolean; | ||
| feedback?: 1 | -1; | ||
| requestId?: string; | ||
| } | ||
|
|
||
| const predefinedPrompts = [ | ||
|
|
@@ -140,7 +141,12 @@ export function Chat() { | |
| setMessages((prevMessages) => | ||
| prevMessages.map((msg) => | ||
| msg.id === loadingMessageId | ||
| ? { ...msg, content: response?.data ?? "", isLoading: false } | ||
| ? { | ||
| ...msg, | ||
| content: response?.data ?? "", | ||
| isLoading: false, | ||
| requestId: response?.requestId, | ||
| } | ||
|
Comment on lines
+144
to
+149
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -e
target='apps/portal/src/components/AI/chat.tsx'
printf '%s\n' '--- target outline ---'
ast-grep outline "$target" 2>/dev/null || true
printf '%s\n' '--- target lines 1-330 ---'
sed -n '1,330p' "$target"
printf '%s\n' '--- direct symbols and request/feedback references ---'
rg -n -C 3 'conversationId|requestId|session_id|feedback|send|Message|RenderAIResponse|useChat|chat' "$target"
printf '%s\n' '--- scoped repository convention files ---'
find /tmp/coderabbit-repo-knowledge/thirdweb-dev-js-e0b0fe64 -type f -name '*.md' -print | sortRepository: thirdweb-dev/js Length of output: 18574 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- AI API files ---'
find apps/portal/src/components/AI -maxdepth 2 -type f -print | sort
printf '%s\n' '--- api implementation and types ---'
api_file=$(find apps/portal/src/components/AI -maxdepth 1 -type f -name 'api.*' -print -quit)
if [ -n "$api_file" ]; then
cat -n "$api_file"
fi
printf '%s\n' '--- target remainder ---'
sed -n '267,430p' apps/portal/src/components/AI/chat.tsx
printf '%s\n' '--- applicable conventions ---'
for f in \
/tmp/coderabbit-repo-knowledge/thirdweb-dev-js-e0b0fe64/conventions/apps.md \
/tmp/coderabbit-repo-knowledge/thirdweb-dev-js-e0b0fe64/conventions/repo-wide.md
do
if [ -f "$f" ]; then
echo "--- $f ---"
cat "$f"
fi
done
printf '%s\n' '--- applicable learnings ---'
find /tmp/coderabbit-repo-knowledge/thirdweb-dev-js-e0b0fe64/learnings -type f -maxdepth 1 -print -exec cat {} \; 2>/dev/null || trueRepository: thirdweb-dev/js Length of output: 15154 🌐 Web query:
💡 Result: In the thirdweb AI API, session_id and request_id are key identifiers used for managing conversation context and tracking API interactions [1][2]. Session ID (session_id) The session_id is used to maintain continuity across a multi-turn conversation [3][4][5]. - When using the HTTP API, it is provided within the context object of the request body (e.g., context: { session_id: "..." }) [3][4]. - If a session_id is not provided, the API automatically generates a new session for the conversation [3][4]. - In the TypeScript SDK and Vercel AI SDK integration, this identifier is often handled automatically or passed via configuration to ensure the model retains memory of previous messages [6][7][5]. Request ID (request_id) The request_id is returned by the API in its responses [1][8]. It serves as a unique identifier for a specific API interaction or transaction preparation step [2][9]. This is particularly useful for debugging or tracking specific actions, such as when the API returns an array of actions (e.g., sign_transaction) [1][2][10]. Feedback While explicit feedback mechanisms can vary based on implementation, the thirdweb AI API allows for continuous interaction where the client can process responses, including transaction actions, and provide subsequent inputs to continue the session [2][9]. If you are looking to build a feedback loop, you would typically capture the request_id along with user input or outcomes to log or analyze the model's performance in your own backend systems [2][9]. Summary of Usage - Context: Use session_id within the context object for continuity [3][4][5]. - Identification: Rely on request_id for logging and tracking specific responses or generated actions [1][2]. Citations:
Serialize sends until the conversation is established. When Queue sends, disable input while establishing the conversation, or store each response's 🤖 Prompt for AI Agents |
||
| : msg, | ||
| ), | ||
| ); | ||
|
|
@@ -262,21 +268,23 @@ function RenderAIResponse(props: { | |
| conversationId: string | undefined; | ||
| message: Message; | ||
| }) { | ||
| const requestId = props.message.requestId; | ||
|
|
||
| const thumbsUpFeedbackMutation = useMutation({ | ||
| mutationFn: () => { | ||
| if (!props.conversationId) { | ||
| if (!props.conversationId || !requestId) { | ||
| throw new Error("No conversation ID"); | ||
| } | ||
| return sendFeedback(props.conversationId, 1); | ||
| return sendFeedback(props.conversationId, requestId, 1); | ||
| }, | ||
| }); | ||
|
|
||
| const thumbsDownFeedbackMutation = useMutation({ | ||
| mutationFn: () => { | ||
| if (!props.conversationId) { | ||
| if (!props.conversationId || !requestId) { | ||
| throw new Error("No conversation ID"); | ||
| } | ||
| return sendFeedback(props.conversationId, -1); | ||
| return sendFeedback(props.conversationId, requestId, -1); | ||
| }, | ||
| }); | ||
|
|
||
|
|
@@ -290,7 +298,7 @@ function RenderAIResponse(props: { | |
| isMessagePending={false} | ||
| /> | ||
|
|
||
| {props.conversationId && ( | ||
| {props.conversationId && requestId && ( | ||
| <div className="mt-4 flex gap-2"> | ||
| <Button | ||
| aria-label="Thumbs up" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: thirdweb-dev/js
Length of output: 22038
Use shared atomic rate-limit storage.
hitsis local to one process. If the portal uses multiple workers or serverless instances, each applies the ten-request limit independently. Requests can therefore exceed the per-IP cap and reach Thirdweb. Move the limiter outside this API module.🤖 Prompt for AI Agents
Source: Coding guidelines