Skip to content

Commit 669bdd9

Browse files
committed
Cleanup ux code
1 parent ae48496 commit 669bdd9

9 files changed

Lines changed: 143 additions & 142 deletions

File tree

ui/components/FlowResponseCard.tsx

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import { FeedbackButton } from "@/components/feedback";
1010
import { Alert, AlertDescription } from "@/components/ui/Alert";
11+
import { getTelemetryIdsFromValue, METADATA_FIELD } from "@/lib/telemetry";
1112

1213
import { MarkdownContainer } from "./MarkdownContainer";
1314
import {
@@ -164,35 +165,13 @@ export default function FlowResponseCard({
164165
? (responseData as Record<string, ResponseData>).outputs || responseData
165166
: responseData || {};
166167

167-
// Extract metadata (span_id, trace_id) from response
168-
const metadata =
169-
responseData && typeof responseData === "object"
170-
? (responseData as Record<string, unknown>).metadata
171-
: null;
172-
173-
const spanId =
174-
metadata && typeof metadata === "object"
175-
? (metadata as Record<string, unknown>).span_id
176-
: null;
177-
178-
const traceId =
179-
metadata && typeof metadata === "object"
180-
? (metadata as Record<string, unknown>).trace_id
181-
: null;
182-
183-
const showFeedback =
184-
feedbackConfig &&
185-
telemetryEnabled &&
186-
spanId &&
187-
traceId &&
188-
typeof spanId === "string" &&
189-
typeof traceId === "string";
168+
const telemetryIds = getTelemetryIdsFromValue(responseData);
190169

191170
return (
192171
<div className="space-y-4">
193172
{responseSchema.properties &&
194173
Object.entries(responseSchema.properties)
195-
.filter(([propertyName]) => propertyName !== "metadata")
174+
.filter(([propertyName]) => propertyName !== METADATA_FIELD)
196175
.map(([propertyName, propertySchema]) => {
197176
const value = (outputsData as Record<string, ResponseData>)[
198177
propertyName
@@ -212,13 +191,12 @@ export default function FlowResponseCard({
212191
);
213192
})}
214193

215-
{showFeedback && (
194+
{feedbackConfig && telemetryEnabled && telemetryIds && (
216195
<div className="pt-4 border-t">
217196
<FeedbackButton
218197
feedbackConfig={feedbackConfig}
219-
spanId={spanId}
220-
traceId={traceId}
221-
telemetryEnabled={telemetryEnabled}
198+
spanId={telemetryIds.spanId}
199+
traceId={telemetryIds.traceId}
222200
/>
223201
</div>
224202
)}

ui/components/FlowResponseTable.tsx

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { useMemo, useState } from "react";
2222
import { FeedbackButton } from "@/components/feedback";
2323
import { Button } from "@/components/ui/Button";
2424
import { Input } from "@/components/ui/Input";
25+
import { getTelemetryIdsFromMetadata, METADATA_FIELD } from "@/lib/telemetry";
2526

2627
import type { SchemaProperty, ResponseData } from "@/types";
2728
import type { FeedbackConfig } from "@/types/FlowMetadata";
@@ -84,42 +85,40 @@ export default function FlowResponseTable({
8485
const columns = useMemo<ColumnDef<Record<string, ResponseData>>[]>(() => {
8586
if (!responseSchema?.properties) return [];
8687

87-
const dataColumns = Object.entries(responseSchema.properties)
88-
.filter(([key]) => key !== "metadata")
89-
.map(([key, schema]) => {
90-
const prop = schema as SchemaProperty;
91-
return {
92-
accessorKey: key,
93-
header: prop.title || key,
94-
cell: ({ row }) => {
95-
const value = row.original[key];
96-
return formatCellValue(value, prop.qtype_type);
97-
},
98-
};
99-
});
88+
const dataColumns: ColumnDef<Record<string, ResponseData>>[] =
89+
Object.entries(responseSchema.properties)
90+
.filter(([key]) => key !== METADATA_FIELD)
91+
.map(([key, schema]) => {
92+
const prop = schema as SchemaProperty;
93+
return {
94+
accessorKey: key,
95+
header: prop.title || key,
96+
cell: (ctx) => {
97+
const value = ctx.row.original[key];
98+
return formatCellValue(value, prop.qtype_type);
99+
},
100+
};
101+
});
100102

101103
// Add feedback column if enabled
102104
if (feedbackConfig && telemetryEnabled) {
103105
dataColumns.push({
104106
id: "feedback",
105107
header: "Feedback",
106-
cell: ({ row }) => {
107-
const metadata = row.original.metadata as
108-
| Record<string, unknown>
109-
| undefined;
110-
const spanId = metadata?.span_id as string | undefined;
111-
const traceId = metadata?.trace_id as string | undefined;
108+
cell: (ctx) => {
109+
const telemetryIds = getTelemetryIdsFromMetadata(
110+
ctx.row.original[METADATA_FIELD],
111+
);
112112

113-
if (!spanId || !traceId) {
113+
if (!telemetryIds) {
114114
return null;
115115
}
116116

117117
return (
118118
<FeedbackButton
119119
feedbackConfig={feedbackConfig}
120-
spanId={spanId}
121-
traceId={traceId}
122-
telemetryEnabled={telemetryEnabled}
120+
spanId={telemetryIds.spanId}
121+
traceId={telemetryIds.traceId}
123122
/>
124123
);
125124
},
@@ -144,18 +143,23 @@ export default function FlowResponseTable({
144143
});
145144

146145
const handleDownloadCSV = () => {
146+
const exportableProperties = Object.entries(
147+
responseSchema?.properties ?? {},
148+
)
149+
.filter(([key]) => key !== METADATA_FIELD)
150+
.map(([key, schema]) => ({
151+
key,
152+
schema: schema as SchemaProperty,
153+
}));
154+
147155
const csvData = table.getFilteredRowModel().rows.map((row) => {
148156
const rowData: Record<string, string> = {};
149-
columns.forEach((col) => {
150-
const key = (col as { accessorKey: string }).accessorKey;
151-
const propertySchema = responseSchema?.properties?.[key] as
152-
| SchemaProperty
153-
| undefined;
154-
rowData[String(col.header)] = formatCellValue(
155-
row.original[key],
156-
propertySchema?.qtype_type,
157-
);
158-
});
157+
158+
for (const { key, schema } of exportableProperties) {
159+
const header = schema.title || key;
160+
rowData[header] = formatCellValue(row.original[key], schema.qtype_type);
161+
}
162+
159163
return rowData;
160164
});
161165

ui/components/chat/MessageBubble.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Bot, User } from "lucide-react";
22

33
import { FeedbackButton } from "@/components/feedback";
44
import { Avatar, AvatarFallback } from "@/components/ui/Avatar";
5+
import { getTelemetryIdsFromMetadata } from "@/lib/telemetry";
56

67
import { MarkdownContainer } from "../MarkdownContainer";
78
import { Thinking } from "../outputs";
@@ -59,17 +60,14 @@ function MessageBubble({
5960
isStreaming,
6061
);
6162

62-
// Extract span_id and trace_id from metadata for feedback
63-
const spanId = message.metadata?.span_id as string | undefined;
64-
const traceId = message.metadata?.trace_id as string | undefined;
63+
const telemetryIds = getTelemetryIdsFromMetadata(message.metadata);
6564

6665
const showFeedback =
6766
!isUser &&
6867
!isStreaming &&
6968
feedbackConfig &&
7069
telemetryEnabled &&
71-
spanId &&
72-
traceId;
70+
telemetryIds;
7371

7472
return (
7573
<div
@@ -123,9 +121,8 @@ function MessageBubble({
123121
<div className="mt-2">
124122
<FeedbackButton
125123
feedbackConfig={feedbackConfig}
126-
spanId={spanId}
127-
traceId={traceId}
128-
telemetryEnabled={telemetryEnabled}
124+
spanId={telemetryIds.spanId}
125+
traceId={telemetryIds.traceId}
129126
/>
130127
</div>
131128
)}

ui/components/feedback/FeedbackButton.tsx

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -16,92 +16,65 @@ import { FeedbackExplanationModal } from "./FeedbackExplanationModal";
1616
import { RatingFeedback } from "./RatingFeedback";
1717
import { ThumbsFeedback } from "./ThumbsFeedback";
1818

19+
import type { FeedbackData, FeedbackSubmission } from "@/types/Feedback";
1920
import type { FeedbackConfig } from "@/types/FlowMetadata";
2021

2122
interface FeedbackButtonProps {
2223
feedbackConfig: FeedbackConfig;
2324
spanId: string;
2425
traceId: string;
25-
telemetryEnabled: boolean;
2626
}
2727

2828
export function FeedbackButton({
2929
feedbackConfig,
3030
spanId,
3131
traceId,
32-
telemetryEnabled,
3332
}: FeedbackButtonProps) {
3433
const [submitted, setSubmitted] = useState(false);
3534
const [isSubmitting, setIsSubmitting] = useState(false);
3635
const [error, setError] = useState<string | null>(null);
3736
const [showExplanation, setShowExplanation] = useState(false);
38-
const [pendingFeedback, setPendingFeedback] = useState<{
39-
type: "thumbs" | "rating" | "category";
40-
value?: boolean;
41-
score?: number;
42-
categories?: string[];
43-
} | null>(null);
44-
45-
if (!telemetryEnabled) {
46-
return null; // Don't show feedback if telemetry is not enabled
47-
}
37+
const [pendingFeedback, setPendingFeedback] = useState<FeedbackData | null>(
38+
null,
39+
);
4840

4941
if (submitted) {
5042
return (
51-
<div className="flex items-center gap-2 text-sm text-green-600">
52-
<Check className="h-4 w-4" />
43+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
44+
<Check className="h-4 w-4 text-primary" />
5345
<span>Feedback submitted</span>
5446
</div>
5547
);
5648
}
5749

5850
if (isSubmitting) {
5951
return (
60-
<div className="flex items-center gap-2 text-sm text-gray-500">
52+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
6153
<Loader2 className="h-4 w-4 animate-spin" />
6254
<span>Submitting...</span>
6355
</div>
6456
);
6557
}
6658

6759
const handleFeedbackSubmit = async (
68-
feedback: {
69-
type: "thumbs" | "rating" | "category";
70-
value?: boolean;
71-
score?: number;
72-
categories?: string[];
73-
},
60+
feedback: FeedbackData,
7461
explanation?: string,
7562
) => {
7663
setIsSubmitting(true);
7764
setError(null);
7865

7966
try {
80-
// Construct feedback data based on type
81-
let feedbackData:
82-
| { type: "thumbs"; value: boolean; explanation?: string }
83-
| { type: "rating"; score: number; explanation?: string }
84-
| { type: "category"; categories: string[]; explanation?: string };
85-
86-
if (feedback.type === "thumbs" && feedback.value !== undefined) {
87-
feedbackData = { type: "thumbs", value: feedback.value, explanation };
88-
} else if (feedback.type === "rating" && feedback.score !== undefined) {
89-
feedbackData = { type: "rating", score: feedback.score, explanation };
90-
} else if (feedback.type === "category" && feedback.categories) {
91-
feedbackData = {
92-
type: "category",
93-
categories: feedback.categories,
94-
explanation,
95-
};
96-
} else {
97-
throw new Error("Invalid feedback data");
98-
}
99-
100-
await apiClient.submitFeedback({
67+
const feedbackWithExplanation: FeedbackData = explanation
68+
? { ...feedback, explanation }
69+
: feedback;
70+
71+
const submission: FeedbackSubmission = {
10172
span_id: spanId,
10273
trace_id: traceId,
103-
feedback: feedbackData,
104-
});
74+
feedback: feedbackWithExplanation,
75+
};
76+
77+
await apiClient.submitFeedback(submission);
10578

10679
setSubmitted(true);
10780
setPendingFeedback(null);
@@ -117,12 +90,7 @@ export function FeedbackButton({
11790
}
11891
};
11992

120-
const handleFeedbackClick = (feedback: {
121-
type: "thumbs" | "rating" | "category";
122-
value?: boolean;
123-
score?: number;
124-
categories?: string[];
125-
}) => {
93+
const handleFeedbackClick = (feedback: FeedbackData) => {
12694
// If explanation is enabled, show modal first
12795
if (feedbackConfig.explanation) {
12896
setPendingFeedback(feedback);
@@ -154,7 +122,7 @@ export function FeedbackButton({
154122
)}
155123
</div>
156124

157-
{error && <div className="text-sm text-red-600">{error}</div>}
125+
{error && <div className="text-sm text-destructive">{error}</div>}
158126

159127
{showExplanation && pendingFeedback && (
160128
<FeedbackExplanationModal

ui/components/feedback/FeedbackExplanationModal.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
"use client";
88

99
import { X } from "lucide-react";
10-
import { useState } from "react";
10+
import { useEffect, useState } from "react";
1111

1212
import { Button } from "@/components/ui/Button";
1313
import { Card } from "@/components/ui/Card";
14+
import { Textarea } from "@/components/ui/textarea";
1415

1516
interface FeedbackExplanationModalProps {
1617
isOpen: boolean;
@@ -25,6 +26,12 @@ export function FeedbackExplanationModal({
2526
}: FeedbackExplanationModalProps) {
2627
const [explanation, setExplanation] = useState("");
2728

29+
useEffect(() => {
30+
if (!isOpen) {
31+
setExplanation("");
32+
}
33+
}, [isOpen]);
34+
2835
if (!isOpen) return null;
2936

3037
const handleSubmit = () => {
@@ -36,7 +43,7 @@ export function FeedbackExplanationModal({
3643
};
3744

3845
return (
39-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
46+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-foreground/20">
4047
<Card className="w-full max-w-md p-6">
4148
<div className="flex items-center justify-between mb-4">
4249
<h3 className="text-lg font-semibold">Add Explanation (Optional)</h3>
@@ -51,11 +58,11 @@ export function FeedbackExplanationModal({
5158
</div>
5259

5360
<div className="space-y-4">
54-
<textarea
61+
<Textarea
5562
value={explanation}
5663
onChange={(e) => setExplanation(e.target.value)}
5764
placeholder="Why did you give this feedback? (optional)"
58-
className="w-full min-h-[100px] p-3 border rounded-md resize-none focus:outline-none focus:ring-2 focus:ring-blue-500"
65+
className="min-h-[100px] resize-none"
5966
autoFocus
6067
/>
6168

0 commit comments

Comments
 (0)