Skip to content

Commit 783e630

Browse files
authored
Merge branch 'main' into main
2 parents b61bf0a + 09d1b8b commit 783e630

7 files changed

Lines changed: 135 additions & 100 deletions

File tree

messages/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"showMore": "Show more",
1313
"generate": "Generate",
1414
"edit": "Edit",
15-
"search": "Search..."
15+
"search": "Search...",
16+
"approve": "Approve",
17+
"reject": "Reject"
1618
},
1719
"Auth": {
1820
"SignIn": {

messages/ko.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"showMore": "더 보기",
1313
"generate": "생성",
1414
"edit": "수정",
15-
"search": "검색..."
15+
"search": "검색...",
16+
"approve": "승인",
17+
"reject": "거절"
1618
},
1719
"Auth": {
1820
"SignIn": {

src/app/api/chat/route.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,13 @@ export async function POST(request: Request) {
178178
const vercelAITooles = safe(tools)
179179
.map((t) => {
180180
if (!t) return undefined;
181-
const bindingTools = {
181+
const bindingTools =
182+
toolChoice === "manual" ? excludeToolExecution(t) : t;
183+
184+
return {
182185
...getAllowedDefaultToolkit(allowedAppDefaultToolkit),
183-
...t,
186+
...bindingTools,
184187
};
185-
if (toolChoice === "manual") {
186-
return excludeToolExecution(bindingTools);
187-
}
188-
return bindingTools;
189188
})
190189
.unwrap();
191190

src/components/markdown.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const components: Partial<Components> = {
6060
},
6161
li: ({ node, children, ...props }) => {
6262
return (
63-
<li className="py-2" {...props}>
63+
<li className="py-2 break-words" {...props}>
6464
<WordByWordFadeIn>{children}</WordByWordFadeIn>
6565
</li>
6666
);

src/components/mention-input.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ export default function MentionInput({
104104
() => ({
105105
immediatelyRender: false,
106106
extensions: [
107-
StarterKit,
107+
StarterKit.configure({
108+
codeBlock: false,
109+
blockquote: false,
110+
code: false,
111+
}),
108112
Mention.configure({
109113
HTMLAttributes: {
110114
class: "mention",

src/components/message-parts.tsx

Lines changed: 117 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ import { UIMessage } from "ai";
44
import {
55
Check,
66
Copy,
7-
ChevronDown,
87
Loader,
98
Pencil,
109
ChevronDownIcon,
1110
RefreshCw,
1211
X,
1312
Wrench,
1413
Trash2,
14+
ChevronRight,
15+
TriangleAlert,
1516
} from "lucide-react";
1617
import { Tooltip, TooltipContent, TooltipTrigger } from "ui/tooltip";
1718
import { Button } from "ui/button";
1819
import { Markdown } from "./markdown";
1920
import { MessagePastesContentCard } from "./message-pasts-content";
20-
import { cn, safeJSONParse, toAny } from "lib/utils";
21+
import { cn, safeJSONParse } from "lib/utils";
2122
import JsonView from "ui/json-view";
2223
import {
2324
useMemo,
@@ -32,7 +33,6 @@ import { MessageEditor } from "./message-editor";
3233
import type { UseChatHelpers } from "@ai-sdk/react";
3334
import { useCopy } from "@/hooks/use-copy";
3435

35-
import { Card, CardContent } from "ui/card";
3636
import { AnimatePresence, motion } from "framer-motion";
3737
import { SelectModel } from "./select-model";
3838
import {
@@ -49,6 +49,9 @@ import { PieChart } from "./tool-invocation/pie-chart";
4949
import { BarChart } from "./tool-invocation/bar-chart";
5050
import { LineChart } from "./tool-invocation/line-chart";
5151
import { PROMPT_PASTE_MAX_LENGTH } from "lib/const";
52+
import { useTranslations } from "next-intl";
53+
import { extractMCPToolId } from "lib/ai/mcp/mcp-tool-id";
54+
import { Separator } from "ui/separator";
5255

5356
type MessagePart = UIMessage["parts"][number];
5457

@@ -175,12 +178,9 @@ export const UserMessagePart = ({
175178
return (
176179
<div className="flex flex-col gap-2 items-end my-2">
177180
<div
178-
onClick={() => {
179-
ref.current?.scrollIntoView({ behavior: "smooth" });
180-
}}
181181
data-testid="message-content"
182182
className={cn(
183-
"flex flex-col gap-4",
183+
"flex flex-col gap-4 max-w-full",
184184
{
185185
"bg-accent text-accent-foreground px-4 py-3 rounded-2xl":
186186
isLast || part.text.length <= PROMPT_PASTE_MAX_LENGTH,
@@ -190,7 +190,7 @@ export const UserMessagePart = ({
190190
)}
191191
>
192192
{isLast || part.text.length <= PROMPT_PASTE_MAX_LENGTH ? (
193-
<p className={cn("whitespace-pre-wrap text-sm")}>
193+
<p className={cn("whitespace-pre-wrap text-sm break-words")}>
194194
<HighlightedText text={part.text} mentions={toolMentions} />
195195
</p>
196196
) : (
@@ -401,9 +401,10 @@ export const ToolMessagePart = memo(
401401
message,
402402
setMessages,
403403
}: ToolMessagePartProps) => {
404+
const t = useTranslations("Common");
404405
const { toolInvocation } = part;
405406
const { toolName, toolCallId, state, args } = toolInvocation;
406-
const [isExpanded, setIsExpanded] = useState(false);
407+
const [expanded, setExpanded] = useState(false);
407408
const { copied: copiedInput, copy: copyInput } = useCopy();
408409
const { copied: copiedOutput, copy: copyOutput } = useCopy();
409410
const [isDeleting, setIsDeleting] = useState(false);
@@ -486,112 +487,138 @@ export const ToolMessagePart = memo(
486487
return null;
487488
}, [state, toolInvocation]);
488489

490+
const { serverName: mcpServerName, toolName: mcpToolName } = useMemo(() => {
491+
return extractMCPToolId(toolName);
492+
}, [toolName]);
493+
494+
const isExpanded = useMemo(() => {
495+
return expanded || result === null;
496+
}, [expanded, result]);
497+
489498
return (
490-
<div key={toolCallId} className="flex flex-col gap-2 group w-full">
499+
<div key={toolCallId} className="group w-full">
491500
{ToolResultComponent ? (
492501
ToolResultComponent
493502
) : (
494-
<>
495-
<div className="flex flex-row gap-2 items-center cursor-pointer">
496-
<Button
497-
onClick={() => setIsExpanded(!isExpanded)}
498-
variant="outline"
499-
className={cn(
500-
"flex flex-row gap-2 justify-between items-center text-muted-foreground min-w-44 bg-card",
501-
isExecuting && "animate-pulse bg-input",
502-
isError && "border-destructive",
503-
)}
504-
>
505-
<Wrench className="size-3.5" />
506-
<p className={cn("font-bold")}>{toolName}</p>
503+
<div className="flex flex-col fade-in duration-300 animate-in">
504+
<div
505+
className="flex gap-2 items-center cursor-pointer group/title"
506+
onClick={() => setExpanded(!expanded)}
507+
>
508+
<div className="p-1.5 text-primary bg-input/40 rounded">
507509
{isExecuting ? (
508-
<Loader className="size-3 animate-spin" />
510+
<Loader className="size-3.5 animate-spin" />
511+
) : isError ? (
512+
<TriangleAlert className="size-3.5 text-destructive" />
509513
) : (
510-
<ChevronDown
511-
className={cn(
512-
isExpanded && "rotate-180",
513-
"transition-transform",
514-
"size-4",
515-
)}
516-
/>
514+
<Wrench className="size-3.5" />
517515
)}
518-
</Button>
519-
{onPoxyToolCall && (
516+
</div>
517+
<span className="font-bold flex items-center gap-2">
518+
{mcpServerName}
519+
</span>
520+
{mcpToolName && (
520521
<>
521-
<Button
522-
variant="outline"
523-
className="bg-input"
524-
size="icon"
525-
onClick={() => onPoxyToolCall(true)}
526-
>
527-
<Check />
528-
</Button>
529-
<Button
530-
variant="outline"
531-
size="icon"
532-
onClick={() => onPoxyToolCall(false)}
533-
>
534-
<X />
535-
</Button>
522+
<ChevronRight className="size-3.5" />
523+
<span className="text-muted-foreground group-hover/title:text-primary transition-colors duration-300">
524+
{mcpToolName}
525+
</span>
536526
</>
537527
)}
528+
<div className="ml-auto group-hover/title:bg-input p-1.5 rounded transition-colors duration-300">
529+
<ChevronDownIcon
530+
className={cn(isExpanded && "rotate-180", "size-3.5")}
531+
/>
532+
</div>
538533
</div>
539-
{isExpanded && (
540-
<Card className="relative mt-2 p-4 max-h-[50vh] overflow-y-auto bg-card">
541-
<CardContent className="flex flex-row gap-4 text-sm ">
542-
<div className="w-1/2 min-w-0 flex flex-col">
543-
<div className="flex items-center gap-2 mb-2 pt-2 pb-1 z-10">
544-
<h5 className="text-muted-foreground text-sm font-medium">
545-
Inputs
546-
</h5>
547-
<div className="flex-1" />
548-
549-
{copiedInput ? (
550-
<Check className="size-4" />
551-
) : (
552-
<Button
553-
variant="ghost"
554-
size="icon"
555-
className="size-3 text-muted-foreground"
556-
onClick={() =>
557-
copyInput(JSON.stringify(toolInvocation.args))
558-
}
559-
>
560-
<Copy />
561-
</Button>
562-
)}
563-
</div>
564-
<JsonView data={toolInvocation.args} />
534+
<div className="flex gap-2 py-2">
535+
<div className="w-7 flex justify-center">
536+
<Separator
537+
orientation="vertical"
538+
className="h-full bg-gradient-to-t from-transparent to-border to-5%"
539+
/>
540+
</div>
541+
<div className="w-full flex flex-col gap-2">
542+
<div className="min-w-0 w-full p-4 rounded-lg bg-card px-4 border text-xs">
543+
<div className="flex items-center">
544+
<h5 className="text-muted-foreground font-medium select-none">
545+
Request
546+
</h5>
547+
<div className="flex-1" />
548+
{copiedInput ? (
549+
<Check className="size-3" />
550+
) : (
551+
<Button
552+
variant="ghost"
553+
size="icon"
554+
className="size-3 text-muted-foreground"
555+
onClick={() =>
556+
copyInput(JSON.stringify(toolInvocation.args))
557+
}
558+
>
559+
<Copy />
560+
</Button>
561+
)}
565562
</div>
566-
567-
<div className="w-1/2 min-w-0 pl-4 flex flex-col">
568-
<div className="flex items-center gap-2 mb-4 pt-2 pb-1 z-10">
569-
<h5 className="text-muted-foreground text-sm font-medium">
570-
Outputs
563+
{isExpanded && (
564+
<div className="p-2 max-h-[300px] overflow-y-auto ">
565+
<JsonView data={toolInvocation.args} />
566+
</div>
567+
)}
568+
</div>
569+
{result && (
570+
<div className="min-w-0 w-full p-4 rounded-lg bg-card px-4 border text-xs mt-2">
571+
<div className="flex items-center">
572+
<h5 className="text-muted-foreground font-medium select-none">
573+
Response
571574
</h5>
572575
<div className="flex-1" />
573576
{copiedOutput ? (
574-
<Check className="size-4" />
577+
<Check className="size-3" />
575578
) : (
576579
<Button
577580
variant="ghost"
578581
size="icon"
579582
className="size-3 text-muted-foreground"
580-
onClick={() =>
581-
copyOutput(
582-
JSON.stringify(toAny(toolInvocation).result),
583-
)
584-
}
583+
onClick={() => copyOutput(JSON.stringify(result))}
585584
>
586585
<Copy />
587586
</Button>
588587
)}
589588
</div>
590-
<JsonView data={result} />
589+
{isExpanded && (
590+
<div className="p-2 max-h-[300px] overflow-y-auto">
591+
<JsonView data={result} />
592+
</div>
593+
)}
591594
</div>
592-
</CardContent>
593-
</Card>
594-
)}
595+
)}
596+
597+
{onPoxyToolCall && (
598+
<div className="flex flex-row gap-2 items-center mt-2">
599+
<Button
600+
variant="secondary"
601+
size="sm"
602+
className="rounded-full text-xs hover:ring"
603+
onClick={() => onPoxyToolCall(true)}
604+
>
605+
<Check />
606+
{t("approve")}
607+
</Button>
608+
<Button
609+
variant="outline"
610+
size="sm"
611+
className="rounded-full text-xs"
612+
onClick={() => onPoxyToolCall(false)}
613+
>
614+
<X />
615+
{t("reject")}
616+
</Button>
617+
</div>
618+
)}
619+
</div>
620+
</div>
621+
595622
{showActions && (
596623
<div className="flex flex-row gap-2 items-center">
597624
<Tooltip>
@@ -616,7 +643,7 @@ export const ToolMessagePart = memo(
616643
</Tooltip>
617644
</div>
618645
)}
619-
</>
646+
</div>
620647
)}
621648
</div>
622649
);

src/lib/keyboard-shortcuts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const toggleVoiceChatShortcut: Shortcut = {
3232
shortcut: {
3333
key: "I",
3434
command: true,
35+
shift: true,
3536
},
3637
};
3738

0 commit comments

Comments
 (0)