langfuse

Форк
0
/
IOPreview.tsx 
179 строк · 5.7 Кб
1
import { JSONView } from "@/src/components/ui/code";
2
import { z } from "zod";
3
import { deepParseJson } from "@/src/utils/json";
4
import { cn } from "@/src/utils/tailwind";
5
import { useState } from "react";
6
import { Button } from "@/src/components/ui/button";
7
import { Tabs, TabsList, TabsTrigger } from "@/src/components/ui/tabs";
8
import { Fragment } from "react";
9

10
export const IOPreview: React.FC<{
11
  input?: unknown;
12
  output?: unknown;
13
  isLoading?: boolean;
14
  hideIfNull?: boolean;
15
}> = ({ isLoading = false, hideIfNull = false, ...props }) => {
16
  const [currentView, setCurrentView] = useState<"pretty" | "json">("pretty");
17

18
  const input = deepParseJson(props.input);
19
  const output = deepParseJson(props.output);
20

21
  // parse old completions: { completion: string } -> string
22
  const outLegacyCompletionSchema = z
23
    .object({
24
      completion: z.string(),
25
    })
26
    .refine((value) => Object.keys(value).length === 1);
27
  const outLegacyCompletionSchemaParsed =
28
    outLegacyCompletionSchema.safeParse(output);
29
  const outputClean = outLegacyCompletionSchemaParsed.success
30
    ? outLegacyCompletionSchemaParsed.data
31
    : props.output ?? null;
32

33
  // OpenAI messages
34
  let inOpenAiMessageArray = OpenAiMessageArraySchema.safeParse(input);
35
  if (!inOpenAiMessageArray.success) {
36
    // check if input is an array of length 1 including an array of OpenAiMessageSchema
37
    // this is the case for some integrations
38
    // e.g. [[OpenAiMessageSchema, ...]]
39
    const inputArray = z.array(OpenAiMessageArraySchema).safeParse(input);
40
    if (inputArray.success && inputArray.data.length === 1) {
41
      inOpenAiMessageArray = OpenAiMessageArraySchema.safeParse(
42
        inputArray.data[0],
43
      );
44
    } else {
45
      // check if input is an object with a messages key
46
      // this is the case for some integrations
47
      // e.g. { messages: [OpenAiMessageSchema, ...] }
48
      const inputObject = z
49
        .object({
50
          messages: OpenAiMessageArraySchema,
51
        })
52
        .safeParse(input);
53

54
      if (inputObject.success) {
55
        inOpenAiMessageArray = OpenAiMessageArraySchema.safeParse(
56
          inputObject.data.messages,
57
        );
58
      }
59
    }
60
  }
61
  const outOpenAiMessage = OpenAiMessageSchema.safeParse(output);
62

63
  // Pretty view available
64
  const isPrettyViewAvailable = inOpenAiMessageArray.success;
65

66
  // default I/O
67
  return (
68
    <>
69
      {isPrettyViewAvailable ? (
70
        <Tabs
71
          value={currentView}
72
          onValueChange={(v) => setCurrentView(v as "pretty" | "json")}
73
        >
74
          <TabsList>
75
            <TabsTrigger value="pretty">Pretty ✨</TabsTrigger>
76
            <TabsTrigger value="json">JSON</TabsTrigger>
77
          </TabsList>
78
        </Tabs>
79
      ) : null}
80
      {isPrettyViewAvailable && currentView === "pretty" ? (
81
        <OpenAiMessageView
82
          messages={inOpenAiMessageArray.data.concat(
83
            outOpenAiMessage.success
84
              ? {
85
                  ...outOpenAiMessage.data,
86
                  role: outOpenAiMessage.data.role ?? "assistant",
87
                }
88
              : {
89
                  role: "assistant",
90
                  content: outputClean ? JSON.stringify(outputClean) : null,
91
                },
92
          )}
93
        />
94
      ) : null}
95
      {currentView === "json" || !isPrettyViewAvailable ? (
96
        <>
97
          {!(hideIfNull && !input) ? (
98
            <JSONView
99
              title="Input"
100
              json={input ?? null}
101
              isLoading={isLoading}
102
              className="flex-1"
103
            />
104
          ) : null}
105
          {!(hideIfNull && !output) ? (
106
            <JSONView
107
              title="Output"
108
              json={outputClean}
109
              isLoading={isLoading}
110
              className="flex-1 bg-green-50"
111
            />
112
          ) : null}
113
        </>
114
      ) : null}
115
    </>
116
  );
117
};
118

119
const OpenAiMessageSchema = z
120
  .object({
121
    role: z.enum(["system", "user", "assistant", "function"]).optional(),
122
    name: z.string().optional(),
123
    content: z
124
      .union([z.record(z.any()), z.record(z.any()).array(), z.string()])
125
      .nullable(),
126
    function_call: z
127
      .object({
128
        name: z.string(),
129
        arguments: z.record(z.any()),
130
      })
131
      .optional(),
132
  })
133
  .strict() // no additional properties
134
  .refine((value) => value.content !== null || value.role !== undefined);
135

136
const OpenAiMessageArraySchema = z.array(OpenAiMessageSchema).min(1);
137

138
const OpenAiMessageView: React.FC<{
139
  messages: z.infer<typeof OpenAiMessageArraySchema>;
140
}> = ({ messages }) => {
141
  const COLLAPSE_THRESHOLD = 3;
142
  const [isCollapsed, setCollapsed] = useState(
143
    messages.length > COLLAPSE_THRESHOLD ? true : null,
144
  );
145

146
  return (
147
    <div className="flex flex-col gap-2 rounded-md border p-3">
148
      {messages
149
        .filter(
150
          (_, i) =>
151
            // show all if not collapsed or null; show first and last n if collapsed
152
            !isCollapsed || i == 0 || i > messages.length - COLLAPSE_THRESHOLD,
153
        )
154
        .map((message, index) => (
155
          <Fragment key={index}>
156
            <JSONView
157
              title={message.name ?? message.role}
158
              json={message.function_call ?? message.content}
159
              className={cn(
160
                message.role === "system" && "bg-gray-100",
161
                message.role === "assistant" && "bg-green-50",
162
              )}
163
            />
164
            {isCollapsed !== null && index === 0 ? (
165
              <Button
166
                variant="ghost"
167
                size="xs"
168
                onClick={() => setCollapsed((v) => !v)}
169
              >
170
                {isCollapsed
171
                  ? `Show ${messages.length - COLLAPSE_THRESHOLD} more ...`
172
                  : "Hide history"}
173
              </Button>
174
            ) : null}
175
          </Fragment>
176
        ))}
177
    </div>
178
  );
179
};
180

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.