feat: 优化输入框交互与添加技能选择按钮
- 重构输入框展开/收缩逻辑,支持 isNewThread 时保持展开 - 添加 onFocusChange 回调支持 - 新增 IframeSkillDialogButton 替换原有的 ModeHoverGuide - 为 AddAttachmentsButton 和 IframeSkillDialogButton 添加 hover 变色效果 - 调整输入框布局结构,移出 PromptInputSubmit 到外层 - 优化 Footer 工具栏的过渡动画 - 添加 selectSkill 国际化文本
This commit is contained in:
parent
4897a4da58
commit
6130f12790
|
|
@ -19,7 +19,6 @@ import {
|
||||||
useThreadChat,
|
useThreadChat,
|
||||||
} from "@/components/workspace/chats";
|
} from "@/components/workspace/chats";
|
||||||
import { DevTodoList } from "@/components/workspace/dev-todo-list";
|
import { DevTodoList } from "@/components/workspace/dev-todo-list";
|
||||||
import { Tooltip } from "@/components/workspace/tooltip";
|
|
||||||
import { InputBox } from "@/components/workspace/input-box";
|
import { InputBox } from "@/components/workspace/input-box";
|
||||||
import { MessageList } from "@/components/workspace/messages";
|
import { MessageList } from "@/components/workspace/messages";
|
||||||
import { ThreadContext } from "@/components/workspace/messages/context";
|
import { ThreadContext } from "@/components/workspace/messages/context";
|
||||||
|
|
@ -69,6 +68,7 @@ export default function ChatPage() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log(thread.values.todos);
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(message: PromptInputMessage) => {
|
(message: PromptInputMessage) => {
|
||||||
|
|
@ -86,25 +86,25 @@ export default function ChatPage() {
|
||||||
<div className="relative flex size-full min-h-0 bg-background justify-between">
|
<div className="relative flex size-full min-h-0 bg-background justify-between">
|
||||||
<header
|
<header
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute top-0 right-0 left-0 z-30 grid grid-cols-3 h-14 px-[20px] py-[20px] shrink-0 items-center rounded-t-[20px]",
|
"absolute top-0 right-0 left-0 z-30 grid grid-cols-3 h-[58px] px-[20px] py-[15px] shrink-0 items-center rounded-t-[20px]",
|
||||||
isNewThread
|
isNewThread
|
||||||
? "bg-background/0 backdrop-blur-none"
|
? "bg-background/0 backdrop-blur-none"
|
||||||
: "bg-background/80 shadow-xs backdrop-blur",
|
: "bg-background/80 shadow-xs backdrop-blur",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* 返回查看结果左箭头 */}
|
{/* 返回查看结果左箭头 */}
|
||||||
<div className=" w-full items-center h-[18px] text-sm font-medium">
|
<div className="flex w-full items-center h-full text-sm font-medium">
|
||||||
<button onClick={() => setShowExitDialog(true)}>
|
<button className="bg-transparent" onClick={() => setShowExitDialog(true)}>
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M3.5 10H13.25H15.6875H16.5M3.5 10L7.5625 6M3.5 10L7.5625 14" stroke="#666666" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
<path d="M3.5 10H13.25H15.6875H16.5M3.5 10L7.5625 6M3.5 10L7.5625 14" stroke="#666666" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className=" w-full text-center items-center h-[18px] text-sm font-medium">
|
<div className="flex w-full justify-center items-center h-full text-sm font-medium overflow-hidden">
|
||||||
<ThreadTitle threadId={threadId} thread={thread} />
|
<ThreadTitle threadId={threadId} thread={thread} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end items-center gap-2">
|
<div className="flex overflow-hidden justify-end items-center gap-2">
|
||||||
<DevTodoList
|
<DevTodoList
|
||||||
className="bg-white"
|
className="bg-white"
|
||||||
todos={thread.values.todos ?? []}
|
todos={thread.values.todos ?? []}
|
||||||
|
|
@ -112,11 +112,9 @@ export default function ChatPage() {
|
||||||
!thread.values.todos || thread.values.todos.length === 0
|
!thread.values.todos || thread.values.todos.length === 0
|
||||||
}
|
}
|
||||||
trigger={
|
trigger={
|
||||||
<Tooltip content="Show Todo">
|
<Button size="sm" variant="ghost" className="text-sm font-medium py-[5px] px-[10px] h-full">
|
||||||
<Button size="sm" variant="ghost" className="text-sm font-medium h-[18px]">
|
<ListTodoIcon className="size-4" /> Todo
|
||||||
<ListTodoIcon className="size-4" /> Todo
|
</Button>
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ArtifactTrigger />
|
<ArtifactTrigger />
|
||||||
|
|
@ -143,7 +141,7 @@ export default function ChatPage() {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<InputBox
|
<InputBox
|
||||||
className={cn("bg-background/5 w-full -translate-y-4")}
|
className={cn("bg-[#FBFAFC] w-full -translate-y-4 ")}
|
||||||
isNewThread={isNewThread}
|
isNewThread={isNewThread}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
autoFocus={isNewThread}
|
autoFocus={isNewThread}
|
||||||
|
|
@ -176,10 +174,12 @@ export default function ChatPage() {
|
||||||
退出后,当前会话结束并销毁,请先下载保存当前结果!
|
退出后,当前会话结束并销毁,请先下载保存当前结果!
|
||||||
</p>
|
</p>
|
||||||
<DevDialogFooter >
|
<DevDialogFooter >
|
||||||
<Button className="w-full " variant="outline" onClick={() => setShowExitDialog(false)}>
|
<Button className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white" variant="ghost" onClick={() => setShowExitDialog(false)}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="w-full bg-[#8E47F0] hover:!bg-[#8E47F0]" onClick={() => setShowExitDialog(false)}>去充值</Button>
|
<Button className="w-full bg-[#f9f8fa] hover:bg-[#8E47F0] hover:text-white" variant="ghost" onClick={() => setShowExitDialog(false)}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
</DevDialogFooter>
|
</DevDialogFooter>
|
||||||
</DevDialogContent>
|
</DevDialogContent>
|
||||||
</DevDialog>
|
</DevDialog>
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ export const ChainOfThoughtContent = memo(
|
||||||
<Collapsible open={isOpen}>
|
<Collapsible open={isOpen}>
|
||||||
<CollapsibleContent
|
<CollapsibleContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-2 space-y-3",
|
"mt-2 space-y-3 bg-[#ffffff]",
|
||||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export const ConversationContent = ({
|
||||||
...props
|
...props
|
||||||
}: ConversationContentProps) => (
|
}: ConversationContentProps) => (
|
||||||
<StickToBottom.Content
|
<StickToBottom.Content
|
||||||
className={cn("flex flex-col gap-8 p-4", className)}
|
className={cn("flex flex-col gap-8 p-[20px]", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
export const Message = ({ className, from, ...props }: MessageProps) => (
|
export const Message = ({ className, from, ...props }: MessageProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex w-full flex-col gap-2",
|
"group flex w-full flex-col gap-2 rounded-[10px] p-[20px]",
|
||||||
from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
|
from === "user" ? "is-user ml-auto justify-end" : "is-assistant bg-[#ffffff]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -457,10 +457,13 @@ export type PromptInputProps = Omit<
|
||||||
message: PromptInputMessage,
|
message: PromptInputMessage,
|
||||||
event: FormEvent<HTMLFormElement>,
|
event: FormEvent<HTMLFormElement>,
|
||||||
) => void | Promise<void>;
|
) => void | Promise<void>;
|
||||||
|
// className for InputGroup (passes through to inner InputGroup component)
|
||||||
|
inputGroupClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PromptInput = ({
|
export const PromptInput = ({
|
||||||
className,
|
className,
|
||||||
|
inputGroupClassName,
|
||||||
accept,
|
accept,
|
||||||
disabled,
|
disabled,
|
||||||
multiple,
|
multiple,
|
||||||
|
|
@ -794,7 +797,7 @@ export const PromptInput = ({
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<InputGroup>{children}</InputGroup>
|
<InputGroup className={inputGroupClassName}>{children}</InputGroup>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -1027,7 +1030,7 @@ export type PromptInputSubmitProps = ComponentProps<typeof InputGroupButton> & {
|
||||||
export const PromptInputSubmit = ({
|
export const PromptInputSubmit = ({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
size = "icon-sm",
|
size = "sm",
|
||||||
status,
|
status,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
|
|
@ -1045,13 +1048,15 @@ export const PromptInputSubmit = ({
|
||||||
return (
|
return (
|
||||||
<InputGroupButton
|
<InputGroupButton
|
||||||
aria-label="Submit"
|
aria-label="Submit"
|
||||||
className={cn(className)}
|
// 被button{bgc:#fff}覆盖了,只能加"!"
|
||||||
|
className={cn(className,'rounded-[10px] w-[140px] h-[40px] text-[#8E47F0] font-bold !bg-[#F0E8FB] hover:!bg-[#8E47F0] hover:text-[#FFFFFF]')}
|
||||||
size={size}
|
size={size}
|
||||||
type="submit"
|
type="submit"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? Icon}
|
{children ?? Icon}
|
||||||
|
发送
|
||||||
</InputGroupButton>
|
</InputGroupButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -186,8 +186,8 @@ export const QueueList = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueListProps) => (
|
}: QueueListProps) => (
|
||||||
<ScrollArea className={cn("-mb-1 mt-2", className)} {...props}>
|
<ScrollArea className={cn("-mb-1", className)} {...props}>
|
||||||
<div className="max-h-40 pr-4">
|
<div className="max-h-40">
|
||||||
<ul>{children}</ul>
|
<ul>{children}</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export const Suggestion = ({
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground cursor-pointer rounded-full px-4 text-xs font-normal",
|
"text-muted-foreground cursor-pointer rounded-full px-[20px] py-[15px] text-xs font-normal",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
|
@ -70,7 +70,7 @@ export const Suggestion = ({
|
||||||
variant={variant}
|
variant={variant}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{Icon && <Icon className="size-4" />}
|
{/* {Icon && <Icon className="size-4" />} */}
|
||||||
{children || suggestion}
|
{children || suggestion}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const buttonVariants = cva(
|
||||||
secondary:
|
secondary:
|
||||||
"cursor-pointer bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"cursor-pointer bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost:
|
ghost:
|
||||||
"cursor-pointer hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"cursor-pointer bg-transparent hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
link: "cursor-pointer text-primary underline-offset-4 hover:underline",
|
link: "cursor-pointer text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
<div
|
<div
|
||||||
data-slot="card"
|
data-slot="card"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6",
|
"bg-card text-card-foreground flex flex-col gap-6 rounded-[20px] not-first:px-[20px] not-first:py-[15px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ function DevDialogContent({
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
data-slot="dev-dialog-content"
|
data-slot="dev-dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-[400px] max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-[40px] shadow-lg duration-200 outline-none sm:max-w-lg",
|
"bg-[#ffffff] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-[400px] max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-[40px] shadow-lg duration-200 outline-none sm:max-w-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,13 @@ function DropdownMenuPortal({
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuTrigger({
|
function DropdownMenuTrigger({
|
||||||
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Trigger
|
<DropdownMenuPrimitive.Trigger
|
||||||
data-slot="dropdown-menu-trigger"
|
data-slot="dropdown-menu-trigger"
|
||||||
|
className={cn(className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
@ -42,7 +44,7 @@ function DropdownMenuContent({
|
||||||
data-slot="dropdown-menu-content"
|
data-slot="dropdown-menu-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-[20px] border p-[20px] shadow-md",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -230,7 +232,7 @@ function DropdownMenuSubContent({
|
||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
data-slot="dropdown-menu-sub-content"
|
data-slot="dropdown-menu-sub-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-[20px] border p-1 shadow-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
export interface DropdownSelectorOption<T extends string> {
|
||||||
|
value: T;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropdownSelectorProps<T extends string> {
|
||||||
|
value: T;
|
||||||
|
options: DropdownSelectorOption<T>[];
|
||||||
|
onChange: (value: T) => void;
|
||||||
|
triggerClassName?: string;
|
||||||
|
contentClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownSelector<T extends string>({
|
||||||
|
value,
|
||||||
|
options,
|
||||||
|
onChange,
|
||||||
|
triggerClassName,
|
||||||
|
contentClassName,
|
||||||
|
}: DropdownSelectorProps<T>) {
|
||||||
|
const selectedOption = options.find((opt) => opt.value === value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger
|
||||||
|
className={triggerClassName ?? "border-none bg-transparent shadow-none select-none focus:outline-none"}
|
||||||
|
>
|
||||||
|
{selectedOption?.label ?? value}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className={contentClassName}>
|
||||||
|
<DropdownMenuRadioGroup value={value} onValueChange={onChange}>
|
||||||
|
{options.map((option) => (
|
||||||
|
<DropdownMenuRadioItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
data-slot="input-group"
|
data-slot="input-group"
|
||||||
role="group"
|
role="group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/input-group border-input/50 dark:bg-background/80 relative flex w-full items-center rounded-md border bg-white/80 shadow-xs transition-[color,box-shadow] outline-none",
|
"group/input-group overflow-hidden border-input/50 dark:bg-background/80 relative flex w-full items-center rounded-md border transition-[color,box-shadow] outline-none",
|
||||||
"h-9 min-w-0 has-[>textarea]:h-auto",
|
"h-9 min-w-0 has-[>textarea]:h-auto",
|
||||||
|
|
||||||
// Variants based on alignment.
|
// Variants based on alignment.
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ function TooltipContent({
|
||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset ?? 4}
|
sideOffset={sideOffset ?? 4}
|
||||||
className={cn(
|
className={cn(
|
||||||
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-foreground text-background dark:text-foreground z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md border px-3 py-1.5 text-xs text-balance shadow-xs dark:border-white/18 dark:bg-[#050504]",
|
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-tooltip-background text-background dark:text-foreground z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md border px-3 py-1.5 text-xs text-balance shadow-xs dark:border-white/18 dark:bg-[#050504]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,7 @@ import {
|
||||||
ArtifactHeader,
|
ArtifactHeader,
|
||||||
ArtifactTitle,
|
ArtifactTitle,
|
||||||
} from "@/components/ai-elements/artifact";
|
} from "@/components/ai-elements/artifact";
|
||||||
import {
|
import { DropdownSelector } from "@/components/ui/dropdown-selector";
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuRadioGroup,
|
|
||||||
DropdownMenuRadioItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||||
import { CodeEditor } from "@/components/workspace/code-editor";
|
import { CodeEditor } from "@/components/workspace/code-editor";
|
||||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||||
|
|
@ -91,6 +85,13 @@ export function ArtifactFileDetail({
|
||||||
|
|
||||||
const displayContent = content ?? "";
|
const displayContent = content ?? "";
|
||||||
|
|
||||||
|
const artifactOptions = useMemo(() => {
|
||||||
|
return (artifacts ?? []).map((artifactPath) => ({
|
||||||
|
value: artifactPath,
|
||||||
|
label: getFileName(artifactPath),
|
||||||
|
}));
|
||||||
|
}, [artifacts]);
|
||||||
|
|
||||||
const [viewMode, setViewMode] = useState<"code" | "preview">("code");
|
const [viewMode, setViewMode] = useState<"code" | "preview">("code");
|
||||||
const [isInstalling, setIsInstalling] = useState(false);
|
const [isInstalling, setIsInstalling] = useState(false);
|
||||||
const { isMock } = useThread();
|
const { isMock } = useThread();
|
||||||
|
|
@ -153,20 +154,11 @@ export function ArtifactFileDetail({
|
||||||
{isWriteFile ? (
|
{isWriteFile ? (
|
||||||
<div className="px-2">{getFileName(filepath)}</div>
|
<div className="px-2">{getFileName(filepath)}</div>
|
||||||
) : (
|
) : (
|
||||||
<DropdownMenu>
|
<DropdownSelector
|
||||||
<DropdownMenuTrigger className="border-none bg-transparent shadow-none select-none focus:outline-none">
|
value={filepath}
|
||||||
{getFileName(filepath)}
|
options={artifactOptions}
|
||||||
</DropdownMenuTrigger>
|
onChange={select}
|
||||||
<DropdownMenuContent>
|
/>
|
||||||
<DropdownMenuRadioGroup value={filepath} onValueChange={select}>
|
|
||||||
{(artifacts ?? []).map((artifactPath) => (
|
|
||||||
<DropdownMenuRadioItem key={artifactPath} value={artifactPath}>
|
|
||||||
{getFileName(artifactPath)}
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuRadioGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
)}
|
)}
|
||||||
</ArtifactTitle>
|
</ArtifactTitle>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -76,14 +76,14 @@ export function ArtifactFileList({
|
||||||
{files.map((file) => (
|
{files.map((file) => (
|
||||||
<Card
|
<Card
|
||||||
key={file}
|
key={file}
|
||||||
className="relative cursor-pointer p-3"
|
className="relative cursor-pointer py-[15px] px-[20px]"
|
||||||
onClick={() => handleClick(file)}
|
onClick={() => handleClick(file)}
|
||||||
>
|
>
|
||||||
<CardHeader className="pr-2 pl-1">
|
<CardHeader className="pr-2 pl-1">
|
||||||
<CardTitle className="relative pl-8">
|
<CardTitle className="relative pl-8">
|
||||||
<div>{getFileName(file)}</div>
|
<div>{getFileName(file)}</div>
|
||||||
<div className="absolute top-2 -left-0.5">
|
<div className="absolute top-2 -left-0.5">
|
||||||
{getFileIcon(file, "size-6")}
|
{getFileIcon(file, "text-[#333333] size-6 stroke-[1.5px]")}
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="pl-8 text-xs">
|
<CardDescription className="pl-8 text-xs">
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ export const ArtifactTrigger = () => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Tooltip content="Show artifacts of this conversation">
|
<Tooltip content="点击可查看生成的文件结果">
|
||||||
<Button
|
<Button
|
||||||
className="text-sm font-medium h-[18px]"
|
className="text-sm font-medium py-[5px] px-[10px] h-full"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setArtifactsOpen(true);
|
setArtifactsOpen(true);
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ import type { GroupImperativeHandle } from "react-resizable-panels";
|
||||||
|
|
||||||
import { ConversationEmptyState } from "@/components/ai-elements/conversation";
|
import { ConversationEmptyState } from "@/components/ai-elements/conversation";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { DropdownSelector } from "@/components/ui/dropdown-selector";
|
||||||
import {
|
import {
|
||||||
ResizableHandle,
|
ResizableHandle,
|
||||||
ResizablePanel,
|
ResizablePanel,
|
||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
} from "@/components/ui/resizable";
|
} from "@/components/ui/resizable";
|
||||||
|
import { getFileName } from "@/core/utils/files";
|
||||||
import { env } from "@/env";
|
import { env } from "@/env";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
|
@ -149,8 +151,19 @@ const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center p-4 pt-8">
|
<div className="flex size-full max-w-(--container-width-sm) flex-col justify-center p-4 pt-8">
|
||||||
<header className="shrink-0">
|
<header className="shrink-0 flex justify-center">
|
||||||
<h2 className="text-lg font-medium">Artifacts</h2>
|
{/* 遍历thread.values.artifacts选择器*/}
|
||||||
|
{thread.values.artifacts && thread.values.artifacts.length > 0 && (
|
||||||
|
<DropdownSelector
|
||||||
|
value={selectedArtifact ?? thread.values.artifacts[0]!}
|
||||||
|
options={thread.values.artifacts.map((artifact) => ({
|
||||||
|
value: artifact,
|
||||||
|
label: getFileName(artifact),
|
||||||
|
}))}
|
||||||
|
onChange={selectArtifact}
|
||||||
|
triggerClassName="text-lg font-medium bg-transparent"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
<main className="min-h-0 grow">
|
<main className="min-h-0 grow">
|
||||||
<ArtifactFileList
|
<ArtifactFileList
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
|
|
||||||
import type { Todo } from "@/core/todos";
|
import type { Todo } from "@/core/todos";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
QueueItem,
|
QueueItem,
|
||||||
|
|
@ -26,10 +27,14 @@ export function DevTodoList({
|
||||||
trigger: React.ReactNode;
|
trigger: React.ReactNode;
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
if (hidden) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
console.log(todos);
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger className={hidden ? "hidden" : ""} asChild>{trigger}</DropdownMenuTrigger>
|
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className={className} align="start" side="top">
|
<DropdownMenuContent className={cn("bg-white z-[100]", className)} align="start" side="top">
|
||||||
<QueueList className="w-64">
|
<QueueList className="w-64">
|
||||||
{todos.map((todo, i) => (
|
{todos.map((todo, i) => (
|
||||||
<QueueItem key={i + (todo.content ?? "")}>
|
<QueueItem key={i + (todo.content ?? "")}>
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,10 @@ export function FlipDisplay({
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
key={uniqueKey}
|
key={uniqueKey}
|
||||||
initial={{ y: 8, opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ y: 2, opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ y: -8, opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
|
className="overflow-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
// transition={{ duration: 0.25, ease: [0.4, 0, 0.2, 1] }}
|
// transition={{ duration: 0.25, ease: [0.4, 0, 0.2, 1] }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ export function InputBox({
|
||||||
onContextChange,
|
onContextChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onStop,
|
onStop,
|
||||||
|
onFocusChange,
|
||||||
...props
|
...props
|
||||||
}: Omit<ComponentProps<typeof PromptInput>, "onSubmit"> & {
|
}: Omit<ComponentProps<typeof PromptInput>, "onSubmit"> & {
|
||||||
assistantId?: string | null;
|
assistantId?: string | null;
|
||||||
|
|
@ -138,6 +139,7 @@ export function InputBox({
|
||||||
) => void;
|
) => void;
|
||||||
onSubmit?: (message: PromptInputMessage) => void;
|
onSubmit?: (message: PromptInputMessage) => void;
|
||||||
onStop?: () => void;
|
onStop?: () => void;
|
||||||
|
onFocusChange?: (focused: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
@ -162,18 +164,22 @@ export function InputBox({
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
// isNewThread 时禁用收缩,始终保持展开
|
||||||
|
const effectiveIsFocused = isNewThread || isFocused;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFocused) return;
|
if (!isFocused) return;
|
||||||
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
||||||
setIsFocused(false);
|
setIsFocused(false);
|
||||||
|
onFocusChange?.(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
}, [isFocused]);
|
}, [isFocused, onFocusChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
|
|
@ -390,29 +396,36 @@ export function InputBox({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={(el) => { promptRootRef.current = el; containerRef.current = el; }} className="relative">
|
<div ref={(el) => { promptRootRef.current = el; containerRef.current = el; }} className="relative">
|
||||||
|
{extraHeader && (
|
||||||
|
<div className="absolute right-0 bottom-full left-0 z-30 flex items-center justify-center pb-4">
|
||||||
|
{extraHeader}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* 输入框主容器 */}
|
||||||
<PromptInput
|
<PromptInput
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background/85 rounded-2xl backdrop-blur-sm transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-2xl",
|
"w-full",
|
||||||
!isFocused && "h-12",
|
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
inputGroupClassName={cn(
|
||||||
|
" backdrop-blur-sm rounded-[20px]",
|
||||||
|
"transition-[height] duration-300 ease-out",
|
||||||
|
!isNewThread && "shadow-[0_0_20px_2px_rgba(0,0,0,0.10)]",
|
||||||
|
effectiveIsFocused ? "h-[200px]" : "h-12",
|
||||||
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
globalDrop
|
globalDrop
|
||||||
multiple
|
multiple
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{extraHeader && (
|
|
||||||
<div className="absolute top-0 right-0 left-0 z-10">
|
|
||||||
<div className="absolute right-0 bottom-0 left-0 flex items-center justify-center">
|
|
||||||
{extraHeader}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<PromptInputAttachments>
|
<PromptInputAttachments>
|
||||||
{(attachment) => <PromptInputAttachment data={attachment} />}
|
{(attachment) => <PromptInputAttachment data={attachment} />}
|
||||||
</PromptInputAttachments>
|
</PromptInputAttachments>
|
||||||
<PromptInputBody className={cn("absolute top-0 right-0 left-0 z-3", !isFocused && "opacity-0 pointer-events-none")}>
|
<PromptInputBody className={cn(
|
||||||
|
"transition-[opacity,transform] duration-300 ease-out",
|
||||||
|
!effectiveIsFocused && "opacity-0 pointer-events-none"
|
||||||
|
)}>
|
||||||
<PromptInputTextarea
|
<PromptInputTextarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
className={cn("size-full")}
|
className={cn("size-full")}
|
||||||
|
|
@ -420,9 +433,13 @@ export function InputBox({
|
||||||
placeholder={t.inputBox.placeholder}
|
placeholder={t.inputBox.placeholder}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
defaultValue={initialValue}
|
defaultValue={initialValue}
|
||||||
|
onFocus={() => {
|
||||||
|
setIsFocused(true);
|
||||||
|
onFocusChange?.(true);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</PromptInputBody>
|
</PromptInputBody>
|
||||||
{!isFocused && (
|
{!effectiveIsFocused && (
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 z-1 cursor-text"
|
className="absolute inset-0 z-1 cursor-text"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -431,11 +448,15 @@ export function InputBox({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PromptInputFooter className={cn("flex", !isFocused && "hidden")}>
|
<PromptInputFooter className={cn(
|
||||||
|
"flex transition-all duration-300 ease-out",
|
||||||
|
// height和padding都为0来隐藏
|
||||||
|
!effectiveIsFocused && "invisible h-[0px] p-[0px] opacity-0 translate-y-2 pointer-events-none"
|
||||||
|
)}>
|
||||||
<PromptInputTools>
|
<PromptInputTools>
|
||||||
<AddAttachmentsButton className="px-2!" />
|
<AddAttachmentsButton className="px-2!" />
|
||||||
<PromptInputActionMenu>
|
<PromptInputActionMenu>
|
||||||
<ModeHoverGuide
|
{/* <ModeHoverGuide
|
||||||
mode={
|
mode={
|
||||||
context.mode === "flash" ||
|
context.mode === "flash" ||
|
||||||
context.mode === "thinking" ||
|
context.mode === "thinking" ||
|
||||||
|
|
@ -457,6 +478,7 @@ export function InputBox({
|
||||||
{context.mode === "ultra" && (
|
{context.mode === "ultra" && (
|
||||||
<RocketIcon className="size-3 text-[#dabb5e]" />
|
<RocketIcon className="size-3 text-[#dabb5e]" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -470,7 +492,8 @@ export function InputBox({
|
||||||
(context.mode === "ultra" && t.inputBox.ultraMode)}
|
(context.mode === "ultra" && t.inputBox.ultraMode)}
|
||||||
</div>
|
</div>
|
||||||
</PromptInputActionMenuTrigger>
|
</PromptInputActionMenuTrigger>
|
||||||
</ModeHoverGuide>
|
</ModeHoverGuide> */}
|
||||||
|
<IframeSkillDialogButton className="px-2!"/>
|
||||||
<PromptInputActionMenuContent className="w-80">
|
<PromptInputActionMenuContent className="w-80">
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
||||||
|
|
@ -746,24 +769,27 @@ export function InputBox({
|
||||||
</ModelSelectorList>
|
</ModelSelectorList>
|
||||||
</ModelSelectorContent>
|
</ModelSelectorContent>
|
||||||
</ModelSelector>
|
</ModelSelector>
|
||||||
<PromptInputSubmit
|
<div className="w-[150px]"></div>
|
||||||
className="rounded-full"
|
|
||||||
disabled={disabled}
|
|
||||||
variant="outline"
|
|
||||||
status={status}
|
|
||||||
/>
|
|
||||||
</PromptInputTools>
|
</PromptInputTools>
|
||||||
</PromptInputFooter>
|
</PromptInputFooter>
|
||||||
{isNewThread && searchParams.get("mode") !== "skill" && (
|
{/* 移动出来 */}
|
||||||
<div className="absolute right-0 -bottom-20 left-0 z-0 flex items-center justify-center">
|
<PromptInputSubmit
|
||||||
<SuggestionList />
|
className="absolute right-3 bottom-3 z-[20]"
|
||||||
</div>
|
disabled={disabled}
|
||||||
)}
|
variant="outline"
|
||||||
|
status={status}
|
||||||
|
/>
|
||||||
{/* TODO: 神秘空div */}
|
{/* TODO: 神秘空div */}
|
||||||
{/* {!isNewThread && (
|
{/* {!isNewThread && (
|
||||||
<div className="bg-background absolute right-0 -bottom-[17px] left-0 z-0 h-4"></div>
|
<div className="bg-background absolute right-0 -bottom-[17px] left-0 z-0 h-4"></div>
|
||||||
)} */}
|
)} */}
|
||||||
</PromptInput>
|
</PromptInput>
|
||||||
|
{/* 小惊喜等 */}
|
||||||
|
{isNewThread && searchParams.get("mode") !== "skill" && (
|
||||||
|
<div className="absolute right-0 bottom-0 left-0 z-0 flex items-center justify-center translate-y-full pt-4">
|
||||||
|
<SuggestionList />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{!disabled &&
|
{!disabled &&
|
||||||
!isNewThread &&
|
!isNewThread &&
|
||||||
|
|
@ -850,14 +876,14 @@ function SuggestionList() {
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Suggestions className="min-h-16 w-fit items-start">
|
<Suggestions className="min-h-16 w-fit items-start">
|
||||||
<ConfettiButton
|
{/* <ConfettiButton
|
||||||
className="text-muted-foreground cursor-pointer rounded-full px-4 text-xs font-normal"
|
className="text-muted-foreground cursor-pointer rounded-full px-[20px] py-[15px] text-xs font-normal"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleSuggestionClick(t.inputBox.surpriseMePrompt)}
|
onClick={() => handleSuggestionClick(t.inputBox.surpriseMePrompt)}
|
||||||
>
|
>
|
||||||
<SparklesIcon className="size-4" /> {t.inputBox.surpriseMe}
|
{t.inputBox.surpriseMe}
|
||||||
</ConfettiButton>
|
</ConfettiButton> */}
|
||||||
{t.inputBox.suggestions.map((suggestion) => (
|
{t.inputBox.suggestions.map((suggestion) => (
|
||||||
<Suggestion
|
<Suggestion
|
||||||
key={suggestion.suggestion}
|
key={suggestion.suggestion}
|
||||||
|
|
@ -900,10 +926,32 @@ function AddAttachmentsButton({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<Tooltip content={t.inputBox.addAttachments}>
|
<Tooltip content={t.inputBox.addAttachments}>
|
||||||
<PromptInputButton
|
<PromptInputButton
|
||||||
className={cn("px-2!", className)}
|
className={cn("group px-2! hover:!bg-[#EAE2F5]", className)}
|
||||||
onClick={() => attachments.openFileDialog()}
|
onClick={() => attachments.openFileDialog()}
|
||||||
>
|
>
|
||||||
<PaperclipIcon className="size-3" />
|
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="transition-[stroke] duration-200 [&>path]:transition-[fill,stroke] [&>path]:duration-200 [&>path:first-child]:group-hover:fill-[#8E47F0] [&>path:last-child]:group-hover:stroke-[#8E47F0]">
|
||||||
|
<path d="M7.05042 7.65254C6.9754 7.72756 6.90039 7.80257 6.90039 7.95258C6.90039 8.02759 6.9754 8.1776 7.05042 8.25262C7.20043 8.40263 7.42545 8.40263 7.57546 8.25262L8.8506 6.97747V10.7279C8.8506 10.9529 9.00061 11.1029 9.22563 11.1029C9.30065 11.1029 9.45066 11.0279 9.52567 11.0279C9.60067 10.9529 9.67568 10.8779 9.67568 10.7279V6.97747L10.9508 8.25262C11.1008 8.40263 11.3259 8.40263 11.4759 8.25262C11.5509 8.1776 11.6259 8.10259 11.6259 7.95258C11.6259 7.87757 11.5509 7.72756 11.4759 7.65254L9.52567 5.70235C9.37564 5.55234 9.15062 5.55234 9.00061 5.70235L7.05042 7.65254Z" fill="#150033"/>
|
||||||
|
<path d="M1.12695 0.5H6.67871C6.87077 0.500077 7.01409 0.574515 7.07324 0.648438L7.09082 0.669922L8.30762 1.88672C8.6222 2.20119 9.01344 2.3681 9.44629 2.36816H16.875C17.2382 2.36842 17.5012 2.63339 17.5 2.99414V13.8848C17.5048 14.2408 17.2454 14.5056 16.8818 14.5059H1.12695C0.764649 14.5057 0.5 14.2401 0.5 13.877V1.12793C0.500049 0.810129 0.702664 0.567404 0.996094 0.511719L1.12695 0.5Z" stroke="#150033"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</PromptInputButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function IframeSkillDialogButton({ className }: { className?: string }) {
|
||||||
|
const { t } = useI18n();
|
||||||
|
const attachments = usePromptInputAttachments();
|
||||||
|
return (
|
||||||
|
<Tooltip content={t.inputBox.selectSkill}>
|
||||||
|
<PromptInputButton
|
||||||
|
className={cn("group px-2! hover:!bg-[#EAE2F5]", className)}
|
||||||
|
onClick={() => attachments.openFileDialog()}
|
||||||
|
>
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="size-4 transition-[stroke] duration-200 [&>path]:transition-[stroke] [&>path]:duration-200 [&>path]:group-hover:stroke-[#8E47F0]" viewBox="0 0 12 16" fill="none">
|
||||||
|
<path d="M3.7998 0.5H9.19922C9.24033 0.5 9.26852 0.518136 9.28516 0.541992C9.30124 0.565318 9.30411 0.588767 9.29395 0.613281H9.29297L7.43066 5.07422L7.1416 5.76758H11.3994C11.4295 5.76765 11.4474 5.77552 11.459 5.7832C11.4724 5.79207 11.4846 5.80503 11.4922 5.82129C11.4997 5.83745 11.5013 5.85253 11.5 5.86328C11.4989 5.87156 11.4953 5.88556 11.4785 5.9043L2.87891 15.4629V15.4639C2.85396 15.4914 2.83406 15.4971 2.82031 15.499C2.80144 15.5016 2.77553 15.4981 2.74902 15.4844C2.72225 15.4705 2.70837 15.453 2.70312 15.4424C2.70056 15.4372 2.69457 15.4253 2.70312 15.3936V15.3926L4.30273 9.49512L4.47461 8.86426H0.600586C0.559682 8.86424 0.531324 8.84587 0.514648 8.82227C0.498608 8.79944 0.496551 8.777 0.505859 8.75293L3.70508 0.558594C3.71075 0.544183 3.72173 0.529788 3.73828 0.518555C3.74688 0.51277 3.75704 0.508037 3.76758 0.504883L3.7998 0.5Z" stroke="#150033"/>
|
||||||
|
</svg>
|
||||||
</PromptInputButton>
|
</PromptInputButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export function MessageGroup({
|
||||||
const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
|
const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
|
||||||
return (
|
return (
|
||||||
<ChainOfThought
|
<ChainOfThought
|
||||||
className={cn("w-full gap-2 rounded-lg border", className)}
|
className={cn("w-full gap-2 rounded-lg bg-white", className)}
|
||||||
open={true}
|
open={true}
|
||||||
>
|
>
|
||||||
{aboveLastToolCallSteps.length > 0 && (
|
{aboveLastToolCallSteps.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,8 @@ export function MessageList({
|
||||||
<Conversation
|
<Conversation
|
||||||
className={cn("flex size-full flex-col justify-center", className)}
|
className={cn("flex size-full flex-col justify-center", className)}
|
||||||
>
|
>
|
||||||
<ConversationContent className="mx-auto w-full max-w-(--container-width-md) gap-8 pt-12">
|
{/* mx-auto max-w-(--container-width-md) w-full */}
|
||||||
|
<ConversationContent className="min-w-(--container-width-xs) gap-8 pt-12">
|
||||||
{groupMessages(messages, (group) => {
|
{groupMessages(messages, (group) => {
|
||||||
if (group.type === "human" || group.type === "assistant") {
|
if (group.type === "human" || group.type === "assistant") {
|
||||||
return group.messages.map((msg) => {
|
return group.messages.map((msg) => {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export function SubtaskCard({
|
||||||
}, [task.status]);
|
}, [task.status]);
|
||||||
return (
|
return (
|
||||||
<ChainOfThought
|
<ChainOfThought
|
||||||
className={cn("relative w-full gap-2 rounded-lg border py-0", className)}
|
className={cn("relative w-full gap-2 rounded-lg py-0", className)}
|
||||||
open={!collapsed}
|
open={!collapsed}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,10 @@ export function Welcome({
|
||||||
`✨ ${t.welcome.createYourOwnSkill} ✨`
|
`✨ ${t.welcome.createYourOwnSkill} ✨`
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className={cn("inline-block", !waved ? "animate-wave" : "")}>
|
{/* <div className={cn("inline-block", !waved ? "animate-wave" : "")}>
|
||||||
{isUltra ? "🚀" : "👋"}
|
{isUltra ? "🚀" : "👋"}
|
||||||
</div>
|
</div> */}
|
||||||
<AuroraText colors={colors}>{t.welcome.greeting}</AuroraText>
|
<AuroraText className="text-[#150033] text-[18px]" colors={colors}>{t.welcome.greeting}</AuroraText>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -59,13 +59,14 @@ export function Welcome({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-muted-foreground text-sm">
|
// <div className="text-muted-foreground text-sm">
|
||||||
{t.welcome.description.includes("\n") ? (
|
// {t.welcome.description.includes("\n") ? (
|
||||||
<pre className="whitespace-pre">{t.welcome.description}</pre>
|
// <pre className="whitespace-pre">{t.welcome.description}</pre>
|
||||||
) : (
|
// ) : (
|
||||||
<p>{t.welcome.description}</p>
|
// <p>{t.welcome.description}</p>
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
|
<div> </div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -104,26 +104,30 @@ export const enUS: Translations = {
|
||||||
followupConfirmReplace: "Replace & send",
|
followupConfirmReplace: "Replace & send",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{
|
{
|
||||||
suggestion: "Write",
|
suggestion: "Paper Writing",
|
||||||
prompt: "Write a blog post about the latest trends on [topic]",
|
prompt: "Write an academic paper about [topic], including abstract, introduction, body and references.",
|
||||||
icon: PenLineIcon,
|
icon: PenLineIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "Research",
|
suggestion: "Report Generation",
|
||||||
prompt:
|
prompt: "Analyze [topic] in depth and generate a well-structured research report.",
|
||||||
"Conduct a deep dive research on [topic], and summarize the findings.",
|
|
||||||
icon: MicroscopeIcon,
|
icon: MicroscopeIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "Collect",
|
suggestion: "Copywriting",
|
||||||
prompt: "Collect data from [source] and create a report.",
|
prompt: "Create a complete planning proposal and promotional copy for [project/event].",
|
||||||
icon: ShapesIcon,
|
icon: ShapesIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "Learn",
|
suggestion: "PPT Generation",
|
||||||
prompt: "Learn about [topic] and create a tutorial.",
|
prompt: "Generate a PPT presentation outline and content about [topic].",
|
||||||
icon: GraduationCapIcon,
|
icon: GraduationCapIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
suggestion: "Document Processing",
|
||||||
|
prompt: "Process [document] with reading, summarizing, translating or format conversion.",
|
||||||
|
icon: CompassIcon,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
suggestionsCreate: [
|
suggestionsCreate: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ export interface Translations {
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
createSkillPrompt: string;
|
createSkillPrompt: string;
|
||||||
addAttachments: string;
|
addAttachments: string;
|
||||||
|
selectSkill:string;
|
||||||
mode: string;
|
mode: string;
|
||||||
flashMode: string;
|
flashMode: string;
|
||||||
flashModeDescription: string;
|
flashModeDescription: string;
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ export const zhCN: Translations = {
|
||||||
|
|
||||||
// Welcome
|
// Welcome
|
||||||
welcome: {
|
welcome: {
|
||||||
greeting: "你好,欢迎回来!",
|
greeting: "使用Skills",
|
||||||
description:
|
description:
|
||||||
"欢迎使用 🦌 DeerFlow,一个完全开源的超级智能体。通过内置和自定义的 Skills,\nDeerFlow 可以帮你搜索网络、分析数据,还能为你生成幻灯片、\n图片、视频、播客及网页等,几乎可以做任何事情。",
|
"欢迎使用 🦌 DeerFlow,一个完全开源的超级智能体。通过内置和自定义的 Skills,\nDeerFlow 可以帮你搜索网络、分析数据,还能为你生成幻灯片、\n图片、视频、播客及网页等,几乎可以做任何事情。",
|
||||||
|
|
||||||
|
|
@ -66,10 +66,11 @@ export const zhCN: Translations = {
|
||||||
|
|
||||||
// Input Box
|
// Input Box
|
||||||
inputBox: {
|
inputBox: {
|
||||||
placeholder: "今天我能为你做些什么?",
|
placeholder: "先输入说明需求,选择Skill,开始使用吧?",
|
||||||
createSkillPrompt:
|
createSkillPrompt:
|
||||||
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
|
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
|
||||||
addAttachments: "添加附件",
|
addAttachments: "添加附件",
|
||||||
|
selectSkill:"选择Skill",
|
||||||
mode: "模式",
|
mode: "模式",
|
||||||
flashMode: "闪速",
|
flashMode: "闪速",
|
||||||
flashModeDescription: "快速且高效的完成任务,但可能不够精准",
|
flashModeDescription: "快速且高效的完成任务,但可能不够精准",
|
||||||
|
|
@ -99,25 +100,30 @@ export const zhCN: Translations = {
|
||||||
followupConfirmReplace: "替换并发送",
|
followupConfirmReplace: "替换并发送",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
{
|
{
|
||||||
suggestion: "写作",
|
suggestion: "论文写作",
|
||||||
prompt: "撰写一篇关于[主题]的博客文章",
|
prompt: "撰写一篇关于[主题]的学术论文,包含摘要、引言、正文和参考文献。",
|
||||||
icon: PenLineIcon,
|
icon: PenLineIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "研究",
|
suggestion: "报告生成",
|
||||||
prompt: "深入浅出的研究一下[主题],并总结发现。",
|
prompt: "深入分析[主题],生成一份结构清晰的调研报告。",
|
||||||
icon: MicroscopeIcon,
|
icon: MicroscopeIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "收集",
|
suggestion: "策划文案",
|
||||||
prompt: "从[来源]收集数据并创建报告。",
|
prompt: "为[项目/活动]撰写一份完整的策划方案和宣传文案。",
|
||||||
icon: ShapesIcon,
|
icon: ShapesIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "学习",
|
suggestion: "PPT生成",
|
||||||
prompt: "学习关于[主题]并创建教程。",
|
prompt: "生成一个关于[主题]的PPT演示文稿大纲和内容。",
|
||||||
icon: GraduationCapIcon,
|
icon: GraduationCapIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
suggestion: "文档处理",
|
||||||
|
prompt: "对[文档]进行阅读、总结、翻译或格式转换等处理。",
|
||||||
|
icon: CompassIcon,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
suggestionsCreate: [
|
suggestionsCreate: [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,7 @@
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--color-tooltip-background: var(--tooltip-background);
|
||||||
--animate-aurora: aurora 8s ease-in-out infinite alternate;
|
--animate-aurora: aurora 8s ease-in-out infinite alternate;
|
||||||
@keyframes aurora {
|
@keyframes aurora {
|
||||||
0% {
|
0% {
|
||||||
|
|
@ -226,17 +227,20 @@
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
--background: #F9F8FA;
|
--background: #F9F8FA;
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: oklch(0.145 0 0);
|
||||||
|
/* --foreground: #00000066; */
|
||||||
/* --card: oklch(1 0.0098 87.47); */
|
/* --card: oklch(1 0.0098 87.47); */
|
||||||
--card: #ffffff;
|
--card: #ffffff;
|
||||||
--card-foreground: oklch(0.145 0 0);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
--popover: oklch(1 0.0098 87.47);
|
/* --popover: oklch(1 0.0098 87.47); */
|
||||||
|
--popover: #ffffff;
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
--primary: oklch(0 0 0);
|
--primary: oklch(0 0 0);
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
/* --secondary: oklch(0.9455 0.0098 87.47); */
|
/* --secondary: oklch(0.9455 0.0098 87.47); */
|
||||||
--secondary: #1500331A;
|
--secondary: #1500331A;
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
--muted: oklch(0.97 0.0098 87.47);
|
/* --muted: oklch(0.97 0.0098 87.47); */
|
||||||
|
--muted: #1500331A;
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
/* --accent: oklch(0.94 0.0098 87.47); */
|
/* --accent: oklch(0.94 0.0098 87.47); */
|
||||||
--accent: #1500331A;
|
--accent: #1500331A;
|
||||||
|
|
@ -258,6 +262,7 @@
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.922 0.0098 87.47);
|
--sidebar-border: oklch(0.922 0.0098 87.47);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
--tooltip-background: #00000066;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
|
@ -292,6 +297,7 @@
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
--tooltip-background: oklch(0.85 0 0);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,6 +393,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar but keep scroll behavior */
|
||||||
|
* {
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari, Opera */
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--container-width-xs: calc(var(--spacing) * 72);
|
--container-width-xs: calc(var(--spacing) * 72);
|
||||||
--container-width-sm: calc(var(--spacing) * 144);
|
--container-width-sm: calc(var(--spacing) * 144);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue