story-kit: merge 178_story_fix_chat_textarea_input_lag
This commit is contained in:
160
frontend/src/components/MessageItem.tsx
Normal file
160
frontend/src/components/MessageItem.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import * as React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import type { Message, ToolCall } from "../types";
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: react-markdown requires any for component props
|
||||
function CodeBlock({ className, children, ...props }: any) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
const isInline = !className;
|
||||
return !isInline && match ? (
|
||||
<SyntaxHighlighter
|
||||
// biome-ignore lint/suspicious/noExplicitAny: oneDark style types are incompatible
|
||||
style={oneDark as any}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
>
|
||||
{String(children).replace(/\n$/, "")}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
interface MessageItemProps {
|
||||
msg: Message;
|
||||
}
|
||||
|
||||
function MessageItemInner({ msg }: MessageItemProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: msg.role === "user" ? "flex-end" : "flex-start",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
padding: msg.role === "user" ? "10px 16px" : "0",
|
||||
borderRadius: msg.role === "user" ? "20px" : "0",
|
||||
background:
|
||||
msg.role === "user"
|
||||
? "#2f2f2f"
|
||||
: msg.role === "tool"
|
||||
? "#222"
|
||||
: "transparent",
|
||||
color: "#ececec",
|
||||
border: msg.role === "tool" ? "1px solid #333" : "none",
|
||||
fontFamily: msg.role === "tool" ? "monospace" : "inherit",
|
||||
fontSize: msg.role === "tool" ? "0.85em" : "1em",
|
||||
fontWeight: "500",
|
||||
whiteSpace: msg.role === "tool" ? "pre-wrap" : "normal",
|
||||
lineHeight: "1.6",
|
||||
}}
|
||||
>
|
||||
{msg.role === "user" ? (
|
||||
msg.content
|
||||
) : msg.role === "tool" ? (
|
||||
<details style={{ cursor: "pointer" }}>
|
||||
<summary
|
||||
style={{
|
||||
color: "#aaa",
|
||||
fontSize: "0.9em",
|
||||
marginBottom: "8px",
|
||||
listStyle: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: "0.8em" }}>▶</span>
|
||||
<span>
|
||||
Tool Output
|
||||
{msg.tool_call_id && ` (${msg.tool_call_id})`}
|
||||
</span>
|
||||
</summary>
|
||||
<pre
|
||||
style={{
|
||||
maxHeight: "300px",
|
||||
overflow: "auto",
|
||||
margin: 0,
|
||||
padding: "8px",
|
||||
background: "#1a1a1a",
|
||||
borderRadius: "4px",
|
||||
fontSize: "0.85em",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{msg.content}
|
||||
</pre>
|
||||
</details>
|
||||
) : (
|
||||
<div className="markdown-body">
|
||||
<Markdown components={{ code: CodeBlock }}>{msg.content}</Markdown>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{msg.tool_calls && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: "12px",
|
||||
fontSize: "0.85em",
|
||||
color: "#aaa",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
{msg.tool_calls.map((tc: ToolCall, i: number) => {
|
||||
let argsSummary = "";
|
||||
try {
|
||||
const args = JSON.parse(tc.function.arguments);
|
||||
const firstKey = Object.keys(args)[0];
|
||||
if (firstKey && args[firstKey]) {
|
||||
argsSummary = String(args[firstKey]);
|
||||
if (argsSummary.length > 50) {
|
||||
argsSummary = `${argsSummary.substring(0, 47)}...`;
|
||||
}
|
||||
}
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`tool-${i}-${tc.function.name}`}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
fontFamily: "monospace",
|
||||
}}
|
||||
>
|
||||
<span style={{ color: "#888" }}>▶</span>
|
||||
<span
|
||||
style={{
|
||||
background: "#333",
|
||||
padding: "2px 6px",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
{tc.function.name}
|
||||
{argsSummary && `(${argsSummary})`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const MessageItem = React.memo(MessageItemInner);
|
||||
Reference in New Issue
Block a user