mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Assorted telemetry related fixes (#46)
This commit is contained in:
parent
23fa6f2217
commit
f34cf9ed5e
@ -328,9 +328,14 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
{() => (
|
{() => (
|
||||||
<SendButton
|
<SendButton
|
||||||
show={(input.length > 0 || uploadedFiles.length > 0) && chatStarted}
|
show={(isStreaming || input.length > 0 || uploadedFiles.length > 0) && chatStarted}
|
||||||
isStreaming={isStreaming}
|
isStreaming={isStreaming}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
|
if (isStreaming) {
|
||||||
|
handleStop?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (input.length > 0 || uploadedFiles.length > 0) {
|
if (input.length > 0 || uploadedFiles.length > 0) {
|
||||||
handleSendMessage?.(event);
|
handleSendMessage?.(event);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import type { FileMap } from '~/lib/stores/files';
|
|||||||
import { shouldIncludeFile } from '~/utils/fileUtils';
|
import { shouldIncludeFile } from '~/utils/fileUtils';
|
||||||
import { getNutLoginKey, submitFeedback } from '~/lib/replay/Problems';
|
import { getNutLoginKey, submitFeedback } from '~/lib/replay/Problems';
|
||||||
import { shouldUseSimulation } from '~/lib/hooks/useSimulation';
|
import { shouldUseSimulation } from '~/lib/hooks/useSimulation';
|
||||||
import { pingTelemetry } from '~/lib/hooks/pingTelemetry';
|
import { ChatMessageTelemetry, pingTelemetry } from '~/lib/hooks/pingTelemetry';
|
||||||
import type { RejectChangeData } from './ApproveChange';
|
import type { RejectChangeData } from './ApproveChange';
|
||||||
|
|
||||||
const toastAnimation = cssTransition({
|
const toastAnimation = cssTransition({
|
||||||
@ -176,6 +176,8 @@ function filterFiles(files: FileMap): FileMap {
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let gActiveChatMessageTelemetry: ChatMessageTelemetry | undefined;
|
||||||
|
|
||||||
export const ChatImpl = memo(
|
export const ChatImpl = memo(
|
||||||
({ description, initialMessages, storeMessageHistory, importChat, exportChat }: ChatProps) => {
|
({ description, initialMessages, storeMessageHistory, importChat, exportChat }: ChatProps) => {
|
||||||
useShortcuts();
|
useShortcuts();
|
||||||
@ -211,6 +213,12 @@ export const ChatImpl = memo(
|
|||||||
initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
|
initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Once we are no longer loading the message is complete.
|
||||||
|
if (gActiveChatMessageTelemetry && !isLoading && !simulationLoading) {
|
||||||
|
gActiveChatMessageTelemetry.finish();
|
||||||
|
gActiveChatMessageTelemetry = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prompt = searchParams.get('prompt');
|
const prompt = searchParams.get('prompt');
|
||||||
|
|
||||||
@ -262,6 +270,11 @@ export const ChatImpl = memo(
|
|||||||
chatStore.setKey('aborted', true);
|
chatStore.setKey('aborted', true);
|
||||||
workbenchStore.abortAllActions();
|
workbenchStore.abortAllActions();
|
||||||
setSimulationLoading(false);
|
setSimulationLoading(false);
|
||||||
|
|
||||||
|
if (gActiveChatMessageTelemetry) {
|
||||||
|
gActiveChatMessageTelemetry.abort("StopButtonClicked");
|
||||||
|
gActiveChatMessageTelemetry = undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -312,7 +325,7 @@ export const ChatImpl = memo(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getEnhancedPrompt = async (userMessage: string) => {
|
const getEnhancedPrompt = async (userMessage: string) => {
|
||||||
let enhancedPrompt, message;
|
let enhancedPrompt, message, hadError = false;
|
||||||
try {
|
try {
|
||||||
const mouseData = getCurrentMouseData();
|
const mouseData = getCurrentMouseData();
|
||||||
enhancedPrompt = await getSimulationEnhancedPrompt(messages, userMessage, mouseData);
|
enhancedPrompt = await getSimulationEnhancedPrompt(messages, userMessage, mouseData);
|
||||||
@ -320,6 +333,7 @@ export const ChatImpl = memo(
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error enhancing prompt", e);
|
console.error("Error enhancing prompt", e);
|
||||||
message = "Error enhancing prompt.";
|
message = "Error enhancing prompt.";
|
||||||
|
hadError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enhancedPromptMessage: Message = {
|
const enhancedPromptMessage: Message = {
|
||||||
@ -328,7 +342,7 @@ export const ChatImpl = memo(
|
|||||||
content: message,
|
content: message,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { enhancedPrompt, enhancedPromptMessage };
|
return { enhancedPrompt, enhancedPromptMessage, hadError };
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendMessage = async (messageInput?: string) => {
|
const sendMessage = async (messageInput?: string) => {
|
||||||
@ -339,6 +353,8 @@ export const ChatImpl = memo(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gActiveChatMessageTelemetry = new ChatMessageTelemetry(messages.length);
|
||||||
|
|
||||||
const loginKey = getNutLoginKey();
|
const loginKey = getNutLoginKey();
|
||||||
|
|
||||||
const apiKeyCookie = Cookies.get(anthropicApiKeyCookieName);
|
const apiKeyCookie = Cookies.get(anthropicApiKeyCookieName);
|
||||||
@ -348,6 +364,8 @@ export const ChatImpl = memo(
|
|||||||
const numFreeUses = +(Cookies.get(anthropicNumFreeUsesCookieName) || 0);
|
const numFreeUses = +(Cookies.get(anthropicNumFreeUsesCookieName) || 0);
|
||||||
if (numFreeUses >= MaxFreeUses) {
|
if (numFreeUses >= MaxFreeUses) {
|
||||||
toast.error('All free uses consumed. Please set a login key or Anthropic API key in the "User Info" settings.');
|
toast.error('All free uses consumed. Please set a login key or Anthropic API key in the "User Info" settings.');
|
||||||
|
gActiveChatMessageTelemetry.abort("NoFreeUses");
|
||||||
|
gActiveChatMessageTelemetry = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +385,7 @@ export const ChatImpl = memo(
|
|||||||
|
|
||||||
let simulationEnhancedPrompt: string | undefined;
|
let simulationEnhancedPrompt: string | undefined;
|
||||||
|
|
||||||
const simulation = await shouldUseSimulation(messages, _input);
|
const simulation = chatStarted && await shouldUseSimulation(messages, _input);
|
||||||
|
|
||||||
if (numAbortsAtStart != gNumAborts) {
|
if (numAbortsAtStart != gNumAborts) {
|
||||||
return;
|
return;
|
||||||
@ -375,9 +393,10 @@ export const ChatImpl = memo(
|
|||||||
|
|
||||||
console.log("UseSimulation", simulation);
|
console.log("UseSimulation", simulation);
|
||||||
|
|
||||||
let didEnhancePrompt = false;
|
let simulationStatus = "NoSimulation";
|
||||||
|
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
|
gActiveChatMessageTelemetry.startSimulation();
|
||||||
|
|
||||||
gLockSimulationData = true;
|
gLockSimulationData = true;
|
||||||
try {
|
try {
|
||||||
await flushSimulationData();
|
await flushSimulationData();
|
||||||
@ -402,11 +421,16 @@ export const ChatImpl = memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
simulationEnhancedPrompt = info.enhancedPrompt;
|
simulationEnhancedPrompt = info.enhancedPrompt;
|
||||||
didEnhancePrompt = true;
|
|
||||||
|
|
||||||
console.log("EnhancedPromptMessage", info.enhancedPromptMessage);
|
console.log("EnhancedPromptMessage", info.enhancedPromptMessage);
|
||||||
setMessages([...messages, info.enhancedPromptMessage]);
|
setMessages([...messages, info.enhancedPromptMessage]);
|
||||||
|
|
||||||
|
simulationStatus = info.hadError ? "PromptError" : "Success";
|
||||||
|
} else {
|
||||||
|
simulationStatus = "RecordingError";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gActiveChatMessageTelemetry.endSimulation(simulationStatus);
|
||||||
} finally {
|
} finally {
|
||||||
gLockSimulationData = false;
|
gLockSimulationData = false;
|
||||||
}
|
}
|
||||||
@ -463,12 +487,7 @@ export const ChatImpl = memo(
|
|||||||
setApproveChangesMessageId(lastMessage.id);
|
setApproveChangesMessageId(lastMessage.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await pingTelemetry("Chat.SendMessage", {
|
gActiveChatMessageTelemetry.sendPrompt(simulationStatus);
|
||||||
numMessages: messages.length,
|
|
||||||
simulation,
|
|
||||||
didEnhancePrompt,
|
|
||||||
loginKey: getNutLoginKey(),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRewind = async (messageId: string, contents: string) => {
|
const onRewind = async (messageId: string, contents: string) => {
|
||||||
@ -486,7 +505,7 @@ export const ChatImpl = memo(
|
|||||||
setMessages(messages.slice(0, messageIndex + 1));
|
setMessages(messages.slice(0, messageIndex + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
await pingTelemetry("Chat.Rewind", {
|
await pingTelemetry("RewindChat", {
|
||||||
numMessages: messages.length,
|
numMessages: messages.length,
|
||||||
rewindIndex: messageIndex,
|
rewindIndex: messageIndex,
|
||||||
loginKey: getNutLoginKey(),
|
loginKey: getNutLoginKey(),
|
||||||
@ -522,7 +541,7 @@ export const ChatImpl = memo(
|
|||||||
|
|
||||||
await flashScreen();
|
await flashScreen();
|
||||||
|
|
||||||
await pingTelemetry("Chat.ApproveChange", {
|
await pingTelemetry("ApproveChange", {
|
||||||
numMessages: messages.length,
|
numMessages: messages.length,
|
||||||
loginKey: getNutLoginKey(),
|
loginKey: getNutLoginKey(),
|
||||||
});
|
});
|
||||||
@ -555,7 +574,7 @@ export const ChatImpl = memo(
|
|||||||
sendMessage(messageContents);
|
sendMessage(messageContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
await pingTelemetry("Chat.RejectChange", {
|
await pingTelemetry("RejectChange", {
|
||||||
retry: data.retry,
|
retry: data.retry,
|
||||||
shareProject: data.shareProject,
|
shareProject: data.shareProject,
|
||||||
shareProjectSuccess,
|
shareProjectSuccess,
|
||||||
|
@ -22,6 +22,14 @@ export function Header() {
|
|||||||
<img src="/logo-styled.svg" alt="logo" className="w-[40px] inline-block rotate-90" />
|
<img src="/logo-styled.svg" alt="logo" className="w-[40px] inline-block rotate-90" />
|
||||||
</a>
|
</a>
|
||||||
<Feedback />
|
<Feedback />
|
||||||
|
<a
|
||||||
|
href="https://www.replay.io/discord"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-bolt-elements-textPrimary hover:text-accent"
|
||||||
|
>
|
||||||
|
<div className="i-ph:discord-logo-fill text-xl" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{chat.started && ( // Display ChatDescription and HeaderActionButtons only when the chat has started.
|
{chat.started && ( // Display ChatDescription and HeaderActionButtons only when the chat has started.
|
||||||
<>
|
<>
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
|
|
||||||
// FIXME ping telemetry server directly instead of going through the server.
|
// FIXME ping telemetry server directly instead of going through the backend.
|
||||||
|
|
||||||
|
import { getNutLoginKey } from "../replay/Problems";
|
||||||
|
|
||||||
|
// We do this to work around CORS insanity.
|
||||||
export async function pingTelemetry(event: string, data: any) {
|
export async function pingTelemetry(event: string, data: any) {
|
||||||
const requestBody: any = {
|
const requestBody: any = {
|
||||||
event,
|
event: "NutChat." + event,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -11,3 +15,45 @@ export async function pingTelemetry(event: string, data: any) {
|
|||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manage telemetry events for a single chat message.
|
||||||
|
|
||||||
|
export class ChatMessageTelemetry {
|
||||||
|
id: string;
|
||||||
|
numMessages: number;
|
||||||
|
|
||||||
|
constructor(numMessages: number) {
|
||||||
|
this.id = Math.random().toString(36).substring(2, 15);
|
||||||
|
this.numMessages = numMessages;
|
||||||
|
this.ping("StartMessage");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ping(event: string, data: any = {}) {
|
||||||
|
pingTelemetry(event, {
|
||||||
|
...data,
|
||||||
|
loginKey: getNutLoginKey(),
|
||||||
|
messageId: this.id,
|
||||||
|
numMessages: this.numMessages,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
this.ping("FinishMessage");
|
||||||
|
}
|
||||||
|
|
||||||
|
abort(reason: string) {
|
||||||
|
this.ping("AbortMessage", { reason });
|
||||||
|
}
|
||||||
|
|
||||||
|
startSimulation() {
|
||||||
|
this.ping("StartSimulation");
|
||||||
|
}
|
||||||
|
|
||||||
|
endSimulation(status: string) {
|
||||||
|
this.ping("EndSimulation", { status });
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPrompt(simulationStatus: string) {
|
||||||
|
this.ping("SendPrompt", { simulationStatus });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,28 +1,39 @@
|
|||||||
import { json, type ActionFunctionArgs } from '@remix-run/cloudflare';
|
import { json, type ActionFunctionArgs } from '@remix-run/cloudflare';
|
||||||
import { getCurrentSpan, wrapWithSpan } from '~/lib/.server/otel';
|
|
||||||
|
async function pingTelemetry(event: string, data: any): Promise<boolean> {
|
||||||
|
console.log("PingTelemetry", event, data);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://telemetry.replay.io/", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ event, ...data }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Telemetry request returned unexpected status: ${response.status}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Telemetry request failed:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
return pingTelemetryAction(args);
|
return pingTelemetryAction(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pingTelemetryAction = wrapWithSpan(
|
async function pingTelemetryAction({ context, request }: ActionFunctionArgs) {
|
||||||
{
|
const { event, data } = await request.json<{
|
||||||
name: "ping-telemetry",
|
event: string;
|
||||||
},
|
data: any;
|
||||||
async function pingTelemetryAction({ context, request }: ActionFunctionArgs) {
|
}>();
|
||||||
const { event, data } = await request.json<{
|
|
||||||
event: string;
|
|
||||||
data: any;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
console.log("PingTelemetry", event, data);
|
const success = await pingTelemetry(event, data);
|
||||||
|
|
||||||
const span = getCurrentSpan();
|
return json({ success });
|
||||||
span?.setAttributes({
|
}
|
||||||
"telemetry.event": event,
|
|
||||||
"telemetry.data": data,
|
|
||||||
});
|
|
||||||
|
|
||||||
return json({ success: true });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
Loading…
Reference in New Issue
Block a user