mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-06 04:19:20 +00:00
docs: inkeep analytics with feedback integration (#5241)
Co-authored-by: Bereket Engida <86073083+Bekacru@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
562da0cc9b
commit
8c45985dec
36
docs/app/api/analytics/conversation/route.ts
Normal file
36
docs/app/api/analytics/conversation/route.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
logConversationToAnalytics,
|
||||||
|
type InkeepMessage,
|
||||||
|
} from "@/lib/inkeep-analytics";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export const runtime = "edge";
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { messages }: { messages: InkeepMessage[] } = await req.json();
|
||||||
|
|
||||||
|
if (!messages || !Array.isArray(messages)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Messages array is required" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await logConversationToAnalytics({
|
||||||
|
type: "openai",
|
||||||
|
messages,
|
||||||
|
properties: {
|
||||||
|
source: "better-auth-docs",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Failed to log conversation" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
docs/app/api/analytics/event/route.ts
Normal file
35
docs/app/api/analytics/event/route.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { logEventToAnalytics } from "@/lib/inkeep-analytics";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export const runtime = "edge";
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { type, entityType, messageId, conversationId } = await req.json();
|
||||||
|
|
||||||
|
if (!type || !entityType) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "type and entityType are required" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entityType !== "message" && entityType !== "conversation") {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "entityType must be 'message' or 'conversation'" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await logEventToAnalytics({
|
||||||
|
type,
|
||||||
|
entityType,
|
||||||
|
messageId,
|
||||||
|
conversationId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json({ error: "Failed to log event" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
37
docs/app/api/analytics/feedback/route.ts
Normal file
37
docs/app/api/analytics/feedback/route.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { submitFeedbackToAnalytics } from "@/lib/inkeep-analytics";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export const runtime = "edge";
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { messageId, type, reasons } = await req.json();
|
||||||
|
|
||||||
|
if (!messageId || !type) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "messageId and type are required" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type !== "positive" && type !== "negative") {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "type must be 'positive' or 'negative'" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await submitFeedbackToAnalytics({
|
||||||
|
messageId,
|
||||||
|
type,
|
||||||
|
reasons,
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Failed to submit feedback" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import { ProvideLinksToolSchema } from "@/lib/chat/inkeep-qa-schema";
|
import { ProvideLinksToolSchema } from "@/lib/chat/inkeep-qa-schema";
|
||||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
||||||
import { convertToModelMessages, streamText } from "ai";
|
import { convertToModelMessages, streamText } from "ai";
|
||||||
|
import {
|
||||||
|
logConversationToAnalytics,
|
||||||
|
type InkeepMessage,
|
||||||
|
} from "@/lib/inkeep-analytics";
|
||||||
|
|
||||||
export const runtime = "edge";
|
export const runtime = "edge";
|
||||||
|
|
||||||
@@ -24,6 +28,61 @@ export async function POST(req: Request) {
|
|||||||
ignoreIncompleteToolCalls: true,
|
ignoreIncompleteToolCalls: true,
|
||||||
}),
|
}),
|
||||||
toolChoice: "auto",
|
toolChoice: "auto",
|
||||||
|
onFinish: async (event) => {
|
||||||
|
try {
|
||||||
|
const extractMessageContent = (msg: any): string => {
|
||||||
|
if (typeof msg.content === "string") {
|
||||||
|
return msg.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.parts && Array.isArray(msg.parts)) {
|
||||||
|
return msg.parts
|
||||||
|
.filter((part: any) => part.type === "text")
|
||||||
|
.map((part: any) => part.text)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.text) {
|
||||||
|
return msg.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const assistantMessageId =
|
||||||
|
event.response.id ||
|
||||||
|
`assistant_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
|
||||||
|
const inkeepMessages: InkeepMessage[] = [
|
||||||
|
...reqJson.messages
|
||||||
|
.map((msg: any) => ({
|
||||||
|
id:
|
||||||
|
msg.id ||
|
||||||
|
`msg_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
||||||
|
role: msg.role,
|
||||||
|
content: extractMessageContent(msg),
|
||||||
|
}))
|
||||||
|
.filter((msg: any) => msg.content.trim() !== ""),
|
||||||
|
{
|
||||||
|
id: assistantMessageId,
|
||||||
|
role: "assistant" as const,
|
||||||
|
content: event.text,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await logConversationToAnalytics({
|
||||||
|
type: "openai",
|
||||||
|
messages: inkeepMessages,
|
||||||
|
properties: {
|
||||||
|
source: "better-auth-docs",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
model: "inkeep-qa-sonnet-4",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Don't fail the request if analytics logging fails
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.toUIMessageStreamResponse();
|
return result.toUIMessageStreamResponse();
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import type { z } from "zod";
|
|||||||
import { DefaultChatTransport } from "ai";
|
import { DefaultChatTransport } from "ai";
|
||||||
import { Markdown } from "./markdown";
|
import { Markdown } from "./markdown";
|
||||||
import { Presence } from "@radix-ui/react-presence";
|
import { Presence } from "@radix-ui/react-presence";
|
||||||
|
import { MessageFeedback } from "./message-feedback";
|
||||||
|
|
||||||
const Context = createContext<{
|
const Context = createContext<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -286,8 +287,16 @@ function ThinkingIndicator() {
|
|||||||
|
|
||||||
function Message({
|
function Message({
|
||||||
message,
|
message,
|
||||||
|
messages,
|
||||||
|
messageIndex,
|
||||||
|
isStreaming,
|
||||||
...props
|
...props
|
||||||
}: { message: UIMessage } & ComponentProps<"div">) {
|
}: {
|
||||||
|
message: UIMessage;
|
||||||
|
messages?: UIMessage[];
|
||||||
|
messageIndex?: number;
|
||||||
|
isStreaming?: boolean;
|
||||||
|
} & ComponentProps<"div">) {
|
||||||
let markdown = "";
|
let markdown = "";
|
||||||
let links: z.infer<typeof ProvideLinksToolSchema>["links"] = [];
|
let links: z.infer<typeof ProvideLinksToolSchema>["links"] = [];
|
||||||
|
|
||||||
@@ -348,6 +357,18 @@ function Message({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{message.role === "assistant" && message.id && !isStreaming && (
|
||||||
|
<MessageFeedback
|
||||||
|
messageId={message.id}
|
||||||
|
userMessageId={
|
||||||
|
messages && messageIndex !== undefined && messageIndex > 0
|
||||||
|
? messages[messageIndex - 1]?.id
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
content={markdown}
|
||||||
|
className="opacity-100 transition-opacity"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -481,9 +502,27 @@ export function AISearchTrigger() {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{chat.messages
|
{chat.messages
|
||||||
.filter((msg: UIMessage) => msg.role !== "system")
|
.filter((msg: UIMessage) => msg.role !== "system")
|
||||||
.map((item: UIMessage) => (
|
.map((item: UIMessage, index: number) => {
|
||||||
<Message key={item.id} message={item} />
|
const filteredMessages = chat.messages.filter(
|
||||||
))}
|
(msg: UIMessage) => msg.role !== "system",
|
||||||
|
);
|
||||||
|
const isLastMessage = index === filteredMessages.length - 1;
|
||||||
|
const isCurrentlyStreaming =
|
||||||
|
(chat.status === "streaming" ||
|
||||||
|
chat.status === "submitted") &&
|
||||||
|
item.role === "assistant" &&
|
||||||
|
isLastMessage;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Message
|
||||||
|
key={item.id}
|
||||||
|
message={item}
|
||||||
|
messages={filteredMessages}
|
||||||
|
messageIndex={index}
|
||||||
|
isStreaming={isCurrentlyStreaming}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
{chat.status === "submitted" && <ThinkingIndicator />}
|
{chat.status === "submitted" && <ThinkingIndicator />}
|
||||||
</div>
|
</div>
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
170
docs/components/message-feedback.tsx
Normal file
170
docs/components/message-feedback.tsx
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Check, Copy, ThumbsDown, ThumbsUp } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { buttonVariants } from "fumadocs-ui/components/ui/button";
|
||||||
|
import {
|
||||||
|
submitFeedbackToInkeep,
|
||||||
|
logEventToInkeep,
|
||||||
|
} from "@/lib/inkeep-analytics";
|
||||||
|
|
||||||
|
interface MessageFeedbackProps {
|
||||||
|
messageId: string;
|
||||||
|
userMessageId?: string;
|
||||||
|
content: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MessageFeedback({
|
||||||
|
messageId,
|
||||||
|
userMessageId,
|
||||||
|
content,
|
||||||
|
className,
|
||||||
|
}: MessageFeedbackProps) {
|
||||||
|
const [feedback, setFeedback] = useState<"positive" | "negative" | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const [isSubmittingFeedback, setIsSubmittingFeedback] = useState(false);
|
||||||
|
const [showSuccessCheckmark, setShowSuccessCheckmark] = useState<
|
||||||
|
"positive" | "negative" | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const handleFeedback = async (type: "positive" | "negative") => {
|
||||||
|
if (isSubmittingFeedback || feedback === type) return;
|
||||||
|
|
||||||
|
const feedbackMessageId = userMessageId || messageId;
|
||||||
|
|
||||||
|
setIsSubmittingFeedback(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await submitFeedbackToInkeep(feedbackMessageId, type, [
|
||||||
|
{
|
||||||
|
label:
|
||||||
|
type === "positive" ? "helpful_response" : "unhelpful_response",
|
||||||
|
details:
|
||||||
|
type === "positive"
|
||||||
|
? "The response was helpful"
|
||||||
|
: "The response was not helpful",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
setFeedback(type);
|
||||||
|
setShowSuccessCheckmark(type);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowSuccessCheckmark(null);
|
||||||
|
}, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
|
setIsSubmittingFeedback(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
if (copied) return;
|
||||||
|
|
||||||
|
const eventMessageId = userMessageId || messageId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(content);
|
||||||
|
setCopied(true);
|
||||||
|
|
||||||
|
await logEventToInkeep("message:copied", "message", eventMessageId);
|
||||||
|
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
} catch (error) {
|
||||||
|
// Silently handle error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-1 mt-3 pt-2 border-t border-fd-border/30",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleFeedback("positive")}
|
||||||
|
disabled={isSubmittingFeedback}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
size: "icon-sm",
|
||||||
|
color: feedback === "positive" ? "primary" : "ghost",
|
||||||
|
className: cn(
|
||||||
|
"h-7 w-7 transition-colors",
|
||||||
|
isSubmittingFeedback && "opacity-50 cursor-not-allowed",
|
||||||
|
feedback === "positive"
|
||||||
|
? "bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400 dark:hover:bg-green-900/50"
|
||||||
|
: "hover:bg-fd-accent hover:text-fd-accent-foreground",
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
title={
|
||||||
|
showSuccessCheckmark === "positive"
|
||||||
|
? "Feedback submitted!"
|
||||||
|
: "Helpful"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{showSuccessCheckmark === "positive" ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-600 animate-in fade-in duration-200" />
|
||||||
|
) : (
|
||||||
|
<ThumbsUp className="h-3.5 w-3.5 transition-all duration-200" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleFeedback("negative")}
|
||||||
|
disabled={isSubmittingFeedback}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
size: "icon-sm",
|
||||||
|
color: feedback === "negative" ? "primary" : "ghost",
|
||||||
|
className: cn(
|
||||||
|
"h-7 w-7 transition-colors",
|
||||||
|
isSubmittingFeedback && "opacity-50 cursor-not-allowed",
|
||||||
|
feedback === "negative"
|
||||||
|
? "bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400 dark:hover:bg-red-900/50"
|
||||||
|
: "hover:bg-fd-accent hover:text-fd-accent-foreground",
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
title={
|
||||||
|
showSuccessCheckmark === "negative"
|
||||||
|
? "Feedback submitted!"
|
||||||
|
: "Not helpful"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{showSuccessCheckmark === "negative" ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-600 animate-in fade-in duration-200" />
|
||||||
|
) : (
|
||||||
|
<ThumbsDown className="h-3.5 w-3.5 transition-all duration-200" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleCopy}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
size: "icon-sm",
|
||||||
|
color: "ghost",
|
||||||
|
className:
|
||||||
|
"h-7 w-7 hover:bg-fd-accent hover:text-fd-accent-foreground transition-colors",
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
title={copied ? "Copied!" : "Copy message"}
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-600" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
158
docs/lib/inkeep-analytics.ts
Normal file
158
docs/lib/inkeep-analytics.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { betterFetch } from "@better-fetch/fetch";
|
||||||
|
|
||||||
|
const INKEEP_ANALYTICS_BASE_URL = "https://api.analytics.inkeep.com";
|
||||||
|
|
||||||
|
export interface InkeepMessage {
|
||||||
|
id?: string;
|
||||||
|
role: "user" | "assistant" | "system";
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InkeepConversation {
|
||||||
|
id?: string;
|
||||||
|
type: "openai";
|
||||||
|
messages: InkeepMessage[];
|
||||||
|
properties?: Record<string, any>;
|
||||||
|
userProperties?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InkeepFeedback {
|
||||||
|
type: "positive" | "negative";
|
||||||
|
messageId: string;
|
||||||
|
reasons?: Array<{
|
||||||
|
label: string;
|
||||||
|
details?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InkeepEvent {
|
||||||
|
type: string;
|
||||||
|
entityType: "message" | "conversation";
|
||||||
|
messageId?: string;
|
||||||
|
conversationId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiKey(): string {
|
||||||
|
const apiKey =
|
||||||
|
process.env.INKEEP_ANALYTICS_API_KEY || process.env.INKEEP_API_KEY;
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error(
|
||||||
|
"INKEEP_ANALYTICS_API_KEY or INKEEP_API_KEY environment variable is required",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeAnalyticsRequest(endpoint: string, data: any) {
|
||||||
|
const apiKey = getApiKey();
|
||||||
|
|
||||||
|
const { data: result, error } = await betterFetch(
|
||||||
|
`${INKEEP_ANALYTICS_BASE_URL}${endpoint}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Inkeep Analytics API error: ${error.status} ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logConversationToAnalytics(
|
||||||
|
conversation: InkeepConversation,
|
||||||
|
) {
|
||||||
|
return await makeAnalyticsRequest("/conversations", conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitFeedbackToAnalytics(feedback: InkeepFeedback) {
|
||||||
|
return await makeAnalyticsRequest("/feedback", feedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logEventToAnalytics(event: InkeepEvent) {
|
||||||
|
return await makeAnalyticsRequest("/events", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logConversationToInkeep(messages: InkeepMessage[]) {
|
||||||
|
try {
|
||||||
|
const { data, error } = await betterFetch("/api/analytics/conversation", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ messages }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to log conversation: ${error.status} - ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitFeedbackToInkeep(
|
||||||
|
messageId: string,
|
||||||
|
type: "positive" | "negative",
|
||||||
|
reasons?: Array<{ label: string; details?: string }>,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { data, error } = await betterFetch("/api/analytics/feedback", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ messageId, type, reasons }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to submit feedback: ${error.status} - ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in submitFeedbackToInkeep:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function logEventToInkeep(
|
||||||
|
type: string,
|
||||||
|
entityType: "message" | "conversation",
|
||||||
|
messageId?: string,
|
||||||
|
conversationId?: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { data, error } = await betterFetch("/api/analytics/event", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ type, entityType, messageId, conversationId }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to log event: ${error.status} - ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user