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:
@@ -17,7 +17,7 @@ import { formatDistanceToNow, format } from "date-fns";
|
||||
import { useVersions } from "@/hooks/useVersions";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { selectedAppIdAtom } from "@/atoms/appAtoms";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -58,6 +58,17 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
|
||||
return null;
|
||||
}, [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
|
||||
const formatTimestamp = (timestamp: string | Date) => {
|
||||
const now = new Date();
|
||||
@@ -199,7 +210,7 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
|
||||
</div>
|
||||
{/* Timestamp and commit info for assistant messages - only visible on hover */}
|
||||
{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">
|
||||
<Clock className="h-3 w-3" />
|
||||
<span>{formatTimestamp(message.createdAt)}</span>
|
||||
@@ -208,16 +219,64 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
|
||||
<div className="flex items-center space-x-1">
|
||||
<GitCommit className="h-3 w-3" />
|
||||
{messageVersion && messageVersion.message && (
|
||||
<span className="max-w-70 truncate font-medium">
|
||||
{
|
||||
messageVersion.message
|
||||
.replace(/^\[dyad\]\s*/i, "")
|
||||
.split("\n")[0]
|
||||
}
|
||||
</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="max-w-50 truncate font-medium">
|
||||
{
|
||||
messageVersion.message
|
||||
.replace(/^\[dyad\]\s*/i, "")
|
||||
.split("\n")[0]
|
||||
}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{messageVersion.message}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</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>
|
||||
|
||||
@@ -67,6 +67,7 @@ export const messages = sqliteTable("messages", {
|
||||
enum: ["approved", "rejected"],
|
||||
}),
|
||||
commitHash: text("commit_hash"),
|
||||
requestId: text("request_id"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
|
||||
@@ -206,7 +206,7 @@ export function registerChatStreamHandlers() {
|
||||
ipcMain.handle("chat:stream", async (event, req: ChatStreamParams) => {
|
||||
try {
|
||||
const fileUploadsState = FileUploadsState.getInstance();
|
||||
|
||||
let dyadRequestId: string | undefined;
|
||||
// Create an AbortController for this stream
|
||||
const abortController = new AbortController();
|
||||
activeStreams.set(req.chatId, abortController);
|
||||
@@ -382,6 +382,12 @@ ${componentSnippet}
|
||||
content: userPrompt,
|
||||
})
|
||||
.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
|
||||
const [placeholderAssistantMessage] = await db
|
||||
@@ -390,6 +396,7 @@ ${componentSnippet}
|
||||
chatId: req.chatId,
|
||||
role: "assistant",
|
||||
content: "", // Start with empty content
|
||||
requestId: dyadRequestId,
|
||||
})
|
||||
.returning();
|
||||
|
||||
@@ -430,7 +437,6 @@ ${componentSnippet}
|
||||
);
|
||||
} else {
|
||||
// Normal AI processing for non-test prompts
|
||||
const settings = readSettings();
|
||||
|
||||
const appPath = getDyadAppPath(updatedChat.app.path);
|
||||
const chatContext = req.selectedComponent
|
||||
@@ -697,7 +703,6 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
} satisfies ModelMessage,
|
||||
];
|
||||
}
|
||||
|
||||
const simpleStreamText = async ({
|
||||
chatMessages,
|
||||
modelClient,
|
||||
@@ -711,7 +716,6 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
systemPromptOverride?: string;
|
||||
dyadDisableFiles?: boolean;
|
||||
}) => {
|
||||
const dyadRequestId = uuidv4();
|
||||
if (isEngineEnabled) {
|
||||
logger.log(
|
||||
"sending AI request to engine with request id:",
|
||||
|
||||
@@ -68,6 +68,7 @@ export interface Message {
|
||||
commitHash?: string | null;
|
||||
dbTimestamp?: string | null;
|
||||
createdAt?: Date | string;
|
||||
requestId?: string | null;
|
||||
}
|
||||
|
||||
export interface Chat {
|
||||
|
||||
Reference in New Issue
Block a user