Copy request id (for Dyad Pro) (#1523)

Based on #1488 by [vedantbhatotia](https://github.com/vedantbhatotia)

<!-- CURSOR_SUMMARY -->
> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is
generating a summary for commit
cc504f6d56ff72407dd5d0135befbf5d9cc695b5. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: vedantbhatotia <vedantbhatotia@gmail.com>
This commit is contained in:
Will Chen
2025-10-13 16:41:46 -07:00
committed by GitHub
parent e99e19e86b
commit ffa4c3ad01
7 changed files with 832 additions and 13 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE `messages` ADD `request_id` text;

View File

@@ -0,0 +1,746 @@
{
"version": "6",
"dialect": "sqlite",
"id": "340e33e4-c82c-44fb-afda-29943bd6bf62",
"prevId": "3c245790-42ce-4d19-9d9d-51ed1a022a7a",
"tables": {
"apps": {
"name": "apps",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"path": {
"name": "path",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"github_org": {
"name": "github_org",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"github_repo": {
"name": "github_repo",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"github_branch": {
"name": "github_branch",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"supabase_project_id": {
"name": "supabase_project_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"neon_project_id": {
"name": "neon_project_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"neon_development_branch_id": {
"name": "neon_development_branch_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"neon_preview_branch_id": {
"name": "neon_preview_branch_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"vercel_project_id": {
"name": "vercel_project_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"vercel_project_name": {
"name": "vercel_project_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"vercel_team_id": {
"name": "vercel_team_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"vercel_deployment_url": {
"name": "vercel_deployment_url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"install_command": {
"name": "install_command",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"start_command": {
"name": "start_command",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"chat_context": {
"name": "chat_context",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"is_favorite": {
"name": "is_favorite",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "0"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"chats": {
"name": "chats",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"app_id": {
"name": "app_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"initial_commit_hash": {
"name": "initial_commit_hash",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {
"chats_app_id_apps_id_fk": {
"name": "chats_app_id_apps_id_fk",
"tableFrom": "chats",
"tableTo": "apps",
"columnsFrom": [
"app_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"language_model_providers": {
"name": "language_model_providers",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"api_base_url": {
"name": "api_base_url",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"env_var_name": {
"name": "env_var_name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"language_models": {
"name": "language_models",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"display_name": {
"name": "display_name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"api_name": {
"name": "api_name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"builtin_provider_id": {
"name": "builtin_provider_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"custom_provider_id": {
"name": "custom_provider_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"max_output_tokens": {
"name": "max_output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"context_window": {
"name": "context_window",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {
"language_models_custom_provider_id_language_model_providers_id_fk": {
"name": "language_models_custom_provider_id_language_model_providers_id_fk",
"tableFrom": "language_models",
"tableTo": "language_model_providers",
"columnsFrom": [
"custom_provider_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"mcp_servers": {
"name": "mcp_servers",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"transport": {
"name": "transport",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"command": {
"name": "command",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"args": {
"name": "args",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"env_json": {
"name": "env_json",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "0"
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"mcp_tool_consents": {
"name": "mcp_tool_consents",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"server_id": {
"name": "server_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"tool_name": {
"name": "tool_name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"consent": {
"name": "consent",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'ask'"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {
"uniq_mcp_consent": {
"name": "uniq_mcp_consent",
"columns": [
"server_id",
"tool_name"
],
"isUnique": true
}
},
"foreignKeys": {
"mcp_tool_consents_server_id_mcp_servers_id_fk": {
"name": "mcp_tool_consents_server_id_mcp_servers_id_fk",
"tableFrom": "mcp_tool_consents",
"tableTo": "mcp_servers",
"columnsFrom": [
"server_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"messages": {
"name": "messages",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"chat_id": {
"name": "chat_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"approval_state": {
"name": "approval_state",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"commit_hash": {
"name": "commit_hash",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"request_id": {
"name": "request_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {
"messages_chat_id_chats_id_fk": {
"name": "messages_chat_id_chats_id_fk",
"tableFrom": "messages",
"tableTo": "chats",
"columnsFrom": [
"chat_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"prompts": {
"name": "prompts",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"versions": {
"name": "versions",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"app_id": {
"name": "app_id",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"commit_hash": {
"name": "commit_hash",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"neon_db_timestamp": {
"name": "neon_db_timestamp",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(unixepoch())"
}
},
"indexes": {
"versions_app_commit_unique": {
"name": "versions_app_commit_unique",
"columns": [
"app_id",
"commit_hash"
],
"isUnique": true
}
},
"foreignKeys": {
"versions_app_id_apps_id_fk": {
"name": "versions_app_id_apps_id_fk",
"tableFrom": "versions",
"tableTo": "apps",
"columnsFrom": [
"app_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@@ -99,6 +99,13 @@
"when": 1759068733234, "when": 1759068733234,
"tag": "0013_damp_mephistopheles", "tag": "0013_damp_mephistopheles",
"breakpoints": true "breakpoints": true
},
{
"idx": 14,
"version": "6",
"when": 1760034009367,
"tag": "0014_needy_vertigo",
"breakpoints": true
} }
] ]
} }

View File

@@ -17,7 +17,7 @@ import { formatDistanceToNow, format } from "date-fns";
import { useVersions } from "@/hooks/useVersions"; import { useVersions } from "@/hooks/useVersions";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { useMemo } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard"; import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
import { import {
Tooltip, Tooltip,
@@ -58,6 +58,17 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
return null; return null;
}, [message.commitHash, message.role, liveVersions]); }, [message.commitHash, message.role, liveVersions]);
// handle copy request id
const [copiedRequestId, setCopiedRequestId] = useState(false);
const copiedRequestIdTimeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
return () => {
if (copiedRequestIdTimeoutRef.current) {
clearTimeout(copiedRequestIdTimeoutRef.current);
}
};
}, []);
// Format the message timestamp // Format the message timestamp
const formatTimestamp = (timestamp: string | Date) => { const formatTimestamp = (timestamp: string | Date) => {
const now = new Date(); const now = new Date();
@@ -199,7 +210,7 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
</div> </div>
{/* Timestamp and commit info for assistant messages - only visible on hover */} {/* Timestamp and commit info for assistant messages - only visible on hover */}
{message.role === "assistant" && message.createdAt && ( {message.role === "assistant" && message.createdAt && (
<div className="mt-1 flex items-center justify-start space-x-2 text-xs text-gray-500 dark:text-gray-400 "> <div className="mt-1 flex flex-wrap items-center justify-start space-x-2 text-xs text-gray-500 dark:text-gray-400 ">
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
<Clock className="h-3 w-3" /> <Clock className="h-3 w-3" />
<span>{formatTimestamp(message.createdAt)}</span> <span>{formatTimestamp(message.createdAt)}</span>
@@ -208,16 +219,64 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
<GitCommit className="h-3 w-3" /> <GitCommit className="h-3 w-3" />
{messageVersion && messageVersion.message && ( {messageVersion && messageVersion.message && (
<span className="max-w-70 truncate font-medium"> <Tooltip>
<TooltipTrigger asChild>
<span className="max-w-50 truncate font-medium">
{ {
messageVersion.message messageVersion.message
.replace(/^\[dyad\]\s*/i, "") .replace(/^\[dyad\]\s*/i, "")
.split("\n")[0] .split("\n")[0]
} }
</span> </span>
</TooltipTrigger>
<TooltipContent>{messageVersion.message}</TooltipContent>
</Tooltip>
)} )}
</div> </div>
)} )}
{message.requestId && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => {
if (!message.requestId) return;
navigator.clipboard
.writeText(message.requestId)
.then(() => {
setCopiedRequestId(true);
if (copiedRequestIdTimeoutRef.current) {
clearTimeout(copiedRequestIdTimeoutRef.current);
}
copiedRequestIdTimeoutRef.current = setTimeout(
() => setCopiedRequestId(false),
2000,
);
})
.catch(() => {
// noop
});
}}
className="flex items-center space-x-1 px-1 py-0.5 hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors duration-200 cursor-pointer"
>
{copiedRequestId ? (
<Check className="h-3 w-3 text-green-500" />
) : (
<Copy className="h-3 w-3" />
)}
<span className="text-xs">
{copiedRequestId ? "Copied" : "Request ID"}
</span>
</button>
</TooltipTrigger>
<TooltipContent>
{copiedRequestId
? "Copied!"
: `Copy Request ID: ${message.requestId.slice(0, 8)}...`}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div> </div>
)} )}
</div> </div>

View File

@@ -67,6 +67,7 @@ export const messages = sqliteTable("messages", {
enum: ["approved", "rejected"], enum: ["approved", "rejected"],
}), }),
commitHash: text("commit_hash"), commitHash: text("commit_hash"),
requestId: text("request_id"),
createdAt: integer("created_at", { mode: "timestamp" }) createdAt: integer("created_at", { mode: "timestamp" })
.notNull() .notNull()
.default(sql`(unixepoch())`), .default(sql`(unixepoch())`),

View File

@@ -206,7 +206,7 @@ export function registerChatStreamHandlers() {
ipcMain.handle("chat:stream", async (event, req: ChatStreamParams) => { ipcMain.handle("chat:stream", async (event, req: ChatStreamParams) => {
try { try {
const fileUploadsState = FileUploadsState.getInstance(); const fileUploadsState = FileUploadsState.getInstance();
let dyadRequestId: string | undefined;
// Create an AbortController for this stream // Create an AbortController for this stream
const abortController = new AbortController(); const abortController = new AbortController();
activeStreams.set(req.chatId, abortController); activeStreams.set(req.chatId, abortController);
@@ -382,6 +382,12 @@ ${componentSnippet}
content: userPrompt, content: userPrompt,
}) })
.returning(); .returning();
const settings = readSettings();
// Only Dyad Pro requests have request ids.
if (settings.enableDyadPro) {
// Generate requestId early so it can be saved with the message
dyadRequestId = uuidv4();
}
// Add a placeholder assistant message immediately // Add a placeholder assistant message immediately
const [placeholderAssistantMessage] = await db const [placeholderAssistantMessage] = await db
@@ -390,6 +396,7 @@ ${componentSnippet}
chatId: req.chatId, chatId: req.chatId,
role: "assistant", role: "assistant",
content: "", // Start with empty content content: "", // Start with empty content
requestId: dyadRequestId,
}) })
.returning(); .returning();
@@ -430,7 +437,6 @@ ${componentSnippet}
); );
} else { } else {
// Normal AI processing for non-test prompts // Normal AI processing for non-test prompts
const settings = readSettings();
const appPath = getDyadAppPath(updatedChat.app.path); const appPath = getDyadAppPath(updatedChat.app.path);
const chatContext = req.selectedComponent const chatContext = req.selectedComponent
@@ -697,7 +703,6 @@ This conversation includes one or more image attachments. When the user uploads
} satisfies ModelMessage, } satisfies ModelMessage,
]; ];
} }
const simpleStreamText = async ({ const simpleStreamText = async ({
chatMessages, chatMessages,
modelClient, modelClient,
@@ -711,7 +716,6 @@ This conversation includes one or more image attachments. When the user uploads
systemPromptOverride?: string; systemPromptOverride?: string;
dyadDisableFiles?: boolean; dyadDisableFiles?: boolean;
}) => { }) => {
const dyadRequestId = uuidv4();
if (isEngineEnabled) { if (isEngineEnabled) {
logger.log( logger.log(
"sending AI request to engine with request id:", "sending AI request to engine with request id:",

View File

@@ -68,6 +68,7 @@ export interface Message {
commitHash?: string | null; commitHash?: string | null;
dbTimestamp?: string | null; dbTimestamp?: string | null;
createdAt?: Date | string; createdAt?: Date | string;
requestId?: string | null;
} }
export interface Chat { export interface Chat {