mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-15 00:39:43 +02:00
small visual adjustments and chat button
This commit is contained in:
@@ -50,12 +50,14 @@ export default function AiSessionLogsPage() {
|
||||
capability?: string;
|
||||
resourceId?: string;
|
||||
actor?: string;
|
||||
model?: string;
|
||||
isStream?: string;
|
||||
}>({
|
||||
providerId: searchParams.get("providerId") || undefined,
|
||||
capability: searchParams.get("capability") || undefined,
|
||||
resourceId: searchParams.get("resourceId") || undefined,
|
||||
actor: searchParams.get("actor") || undefined,
|
||||
model: searchParams.get("model") || undefined,
|
||||
isStream: searchParams.get("isStream") || undefined
|
||||
});
|
||||
|
||||
@@ -132,7 +134,8 @@ export default function AiSessionLogsPage() {
|
||||
const filterAttributes = data?.filterAttributes ?? {
|
||||
providers: [],
|
||||
resources: [],
|
||||
users: []
|
||||
users: [],
|
||||
models: []
|
||||
};
|
||||
|
||||
const handleDateRangeChange = (
|
||||
@@ -309,15 +312,27 @@ export default function AiSessionLogsPage() {
|
||||
},
|
||||
{
|
||||
accessorKey: "requestedModel",
|
||||
header: ({ column }) => (
|
||||
<span className="px-2">{t("model")}</span>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{row.original.requestedModel || "-"}
|
||||
</span>
|
||||
<div className="flex items-center gap-2 px-2">
|
||||
<ColumnFilterButton
|
||||
options={filterAttributes.models.map((model) => ({
|
||||
value: model,
|
||||
label: model
|
||||
}))}
|
||||
selectedValue={filters.model}
|
||||
onValueChange={(value) =>
|
||||
handleFilterChange("model", value)
|
||||
}
|
||||
label={t("model")}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
emptyMessage={t("emptySearchOptions")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return <span>{row.original.requestedModel || "-"}</span>;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -444,7 +459,7 @@ export default function AiSessionLogsPage() {
|
||||
const renderExpandedRow = (row: any) => {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 text-xs">
|
||||
<div className="grid grid-cols-2 gap-4 text-xs">
|
||||
<div>
|
||||
<strong>{t("aiSessionId")}</strong>
|
||||
<p className="text-muted-foreground mt-1 break-all">
|
||||
@@ -457,18 +472,6 @@ export default function AiSessionLogsPage() {
|
||||
{row.statusCode ?? "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{t("model")}</strong>
|
||||
<p className="text-muted-foreground mt-1 break-all">
|
||||
{row.requestedModel || "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{t("capability")}</strong>
|
||||
<p className="text-muted-foreground mt-1 break-all">
|
||||
{capabilityLabels[row.capability] || row.capability}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<AiSessionChatView
|
||||
normalizedRequest={row.normalizedRequest}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { AlertTriangle, Bot, Terminal, User as UserIcon, Wrench } from "lucide-react";
|
||||
import {
|
||||
AlertTriangle,
|
||||
Bot,
|
||||
Code,
|
||||
MessagesSquare,
|
||||
Terminal,
|
||||
User as UserIcon,
|
||||
Wrench
|
||||
} from "lucide-react";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import type { NormalizedAiMessage } from "@server/lib/aiMessageNormalization";
|
||||
|
||||
type AiSessionChatViewProps = {
|
||||
@@ -95,14 +104,14 @@ function RawFallbackBlock({
|
||||
label: string;
|
||||
raw: string | null;
|
||||
noDataLabel: string;
|
||||
unparsedLabel: string;
|
||||
unparsedLabel?: string;
|
||||
}) {
|
||||
const pretty = prettyRaw(raw);
|
||||
return (
|
||||
<div className="rounded-md border bg-muted/30 p-3">
|
||||
<div className="mb-1 text-xs font-medium text-muted-foreground">
|
||||
{label}
|
||||
{pretty && (
|
||||
{pretty && unparsedLabel && (
|
||||
<span className="ml-2 font-normal italic opacity-70">
|
||||
{unparsedLabel}
|
||||
</span>
|
||||
@@ -123,6 +132,7 @@ export function AiSessionChatView({
|
||||
truncated
|
||||
}: AiSessionChatViewProps) {
|
||||
const t = useTranslations();
|
||||
const [rawMode, setRawMode] = useState(false);
|
||||
|
||||
const requestMessages = useMemo(
|
||||
() => parseMessages(normalizedRequest),
|
||||
@@ -139,38 +149,69 @@ export function AiSessionChatView({
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{truncated && (
|
||||
<div className="flex items-center gap-2 text-xs text-amber-600 dark:text-amber-500">
|
||||
<AlertTriangle className="h-3.5 w-3.5 flex-none" />
|
||||
{t("aiSessionLogTruncated")}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex max-h-[32rem] flex-col gap-3 overflow-y-auto rounded-md border bg-background p-4">
|
||||
{hasRequestMessages ? (
|
||||
requestMessages!.map((message, i) => (
|
||||
<MessageBubble key={`req-${i}`} message={message} />
|
||||
))
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{truncated ? (
|
||||
<div className="flex items-center gap-2 text-xs text-amber-600 dark:text-amber-500">
|
||||
<AlertTriangle className="h-3.5 w-3.5 flex-none" />
|
||||
{t("aiSessionLogTruncated")}
|
||||
</div>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setRawMode((prev) => !prev)}
|
||||
>
|
||||
{rawMode ? (
|
||||
<MessagesSquare className="mr-2 h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Code className="mr-2 h-3.5 w-3.5" />
|
||||
)}
|
||||
{rawMode ? t("aiSessionViewChat") : t("aiSessionViewRaw")}
|
||||
</Button>
|
||||
</div>
|
||||
{rawMode ? (
|
||||
<div className="flex max-h-[32rem] flex-col gap-3 overflow-y-auto rounded-md border bg-background p-4">
|
||||
<RawFallbackBlock
|
||||
label={t("aiSessionRequest")}
|
||||
raw={requestBody}
|
||||
raw={normalizedRequest}
|
||||
noDataLabel={t("aiSessionNoData")}
|
||||
unparsedLabel={t("aiSessionCouldNotParse")}
|
||||
/>
|
||||
)}
|
||||
{hasResponseMessages ? (
|
||||
responseMessages!.map((message, i) => (
|
||||
<MessageBubble key={`res-${i}`} message={message} />
|
||||
))
|
||||
) : (
|
||||
<RawFallbackBlock
|
||||
label={t("aiSessionResponse")}
|
||||
raw={responseBody}
|
||||
raw={normalizedResponse}
|
||||
noDataLabel={t("aiSessionNoData")}
|
||||
unparsedLabel={t("aiSessionCouldNotParse")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex max-h-[32rem] flex-col gap-3 overflow-y-auto rounded-md border bg-background p-4">
|
||||
{hasRequestMessages ? (
|
||||
requestMessages!.map((message, i) => (
|
||||
<MessageBubble key={`req-${i}`} message={message} />
|
||||
))
|
||||
) : (
|
||||
<RawFallbackBlock
|
||||
label={t("aiSessionRequest")}
|
||||
raw={requestBody}
|
||||
noDataLabel={t("aiSessionNoData")}
|
||||
unparsedLabel={t("aiSessionCouldNotParse")}
|
||||
/>
|
||||
)}
|
||||
{hasResponseMessages ? (
|
||||
responseMessages!.map((message, i) => (
|
||||
<MessageBubble key={`res-${i}`} message={message} />
|
||||
))
|
||||
) : (
|
||||
<RawFallbackBlock
|
||||
label={t("aiSessionResponse")}
|
||||
raw={responseBody}
|
||||
noDataLabel={t("aiSessionNoData")}
|
||||
unparsedLabel={t("aiSessionCouldNotParse")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1053,6 +1053,7 @@ export const aiSessionLogsFiltersSchema = z.object({
|
||||
capability: z.string().optional().catch(undefined),
|
||||
resourceId: z.string().optional().catch(undefined),
|
||||
actor: z.string().optional().catch(undefined),
|
||||
model: z.string().optional().catch(undefined),
|
||||
isStream: z.string().optional().catch(undefined)
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user