This commit is contained in:
xuhd committed 2026-05-14 20:45:02 +08:00
commit 9a67ca7c03
97 files changed
+20759 -306

No files matched your search

@@ -6,7 +6,7 @@ import {
type ReactNode,
} from "react";
import { useSidebar } from "@/components/ui/sidebar";
import { useSidebarSafe } from "@/components/ui/sidebar";
import { env } from "@/env";
export interface ArtifactsContextType {
@@ -39,19 +39,19 @@ export function ArtifactsProvider({ children }: ArtifactsProviderProps) {
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true",
);
const [autoOpen, setAutoOpen] = useState(true);
const { setOpen: setSidebarOpen } = useSidebar();
const sidebar = useSidebarSafe();
const select = useCallback(
(artifact: string, autoSelect = false) => {
setSelectedArtifact(artifact);
if (env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true") {
setSidebarOpen(false);
sidebar?.setOpen(false);
}
if (!autoSelect) {
setAutoSelect(false);
}
},
[setSidebarOpen, setSelectedArtifact, setAutoSelect],
[sidebar, setSelectedArtifact, setAutoSelect],
);
const deselect = useCallback(() => {
@@ -80,12 +80,16 @@ import {
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { useRecommendedQuestions } from "@/core/recommended-questions/hooks";
import { useThread } from "./messages/context";
import { ModeHoverGuide } from "./mode-hover-guide";
import { Tooltip } from "./tooltip";
type InputMode = "flash" | "thinking" | "pro" | "ultra";
function getResolvedMode(
mode: InputMode | undefined,
supportsThinking: boolean,
@@ -161,6 +165,8 @@ export function InputBox({
null,
);
const { questions: recommendedQuestions } = useRecommendedQuestions();
useEffect(() => {
if (models.length === 0) {
return;
@@ -430,7 +436,32 @@ export function InputBox({
}, [context.model_name, disabled, isMock, status, thread.messages, threadId]);
return (
<div ref={promptRootRef} className="relative flex flex-col gap-4">
<div ref={promptRootRef} className="relative flex flex-col gap-6">
{extraHeader && (
<div className="flex items-center justify-center">
{extraHeader}
</div>
)}
{isNewThread && searchParams.get("mode") !== "skill" && recommendedQuestions.length > 0 && (
<div className="flex flex-wrap justify-center gap-2.5 px-4 mb-[10px]">
{recommendedQuestions.map((q) => (
<button
key={q.id}
type="button"
className="rounded-full border border-[#e4e4e7] bg-white px-4 py-2 text-sm text-[#18181b] shadow-none transition-colors hover:bg-[#f4f4f5] cursor-pointer dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700"
onClick={() => {
textInput.setInput(q.content);
setFollowupsHidden(true);
setTimeout(() => requestFormSubmit(), 0);
}}
>
{q.title}
</button>
))}
</div>
)}
{showFollowups && (
<div className="flex items-center justify-center pb-2">
<div className="flex items-center gap-2">
@@ -464,7 +495,7 @@ export function InputBox({
)}
<PromptInput
className={cn(
"rounded-[1.85rem] border border-[#eadfce] bg-white shadow-[0_24px_70px_-34px_rgba(120,83,42,0.32)] ring-1 ring-[#f6efe5] transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-[1.85rem]",
"rounded-[1.85rem] border border-[#eadfce] bg-white shadow-[0_24px_70px_-34px_rgba(120,83,42,0.32)] ring-1 ring-[#f6efe5] transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-[1.85rem] dark:bg-slate-800 dark:border-slate-700 dark:ring-slate-700 dark:shadow-none",
className,
)}
disabled={disabled}
@@ -473,20 +504,13 @@ export function InputBox({
onSubmit={handleSubmit}
{...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>
{(attachment) => <PromptInputAttachment data={attachment} />}
</PromptInputAttachments>
<PromptInputBody className="absolute top-0 right-0 left-0 z-3">
<PromptInputTextarea
className={cn(
"size-full px-2 py-1 text-[15px] leading-7 placeholder:text-[#b7ab9a]",
"size-full px-2 py-1 text-[15px] leading-7 placeholder:text-[#b7ab9a] dark:placeholder:text-slate-500",
)}
disabled={disabled}
placeholder={t.inputBox.placeholder}
@@ -494,7 +518,7 @@ export function InputBox({
defaultValue={initialValue}
/>
</PromptInputBody>
<PromptInputFooter className="flex items-center border-t border-[#f4ede4] px-2 pt-2.5 pb-1.5">
<PromptInputFooter className="flex items-center border-t border-[#f4ede4] dark:border-slate-700 px-2 pt-2.5 pb-1.5">
<PromptInputTools className="gap-1.5">
{/* TODO: Add more connectors here
<PromptInputActionMenu>
@@ -505,7 +529,7 @@ export function InputBox({
/>
</PromptInputActionMenuContent>
</PromptInputActionMenu> */}
<AddAttachmentsButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-2.5! shadow-none hover:bg-white" />
<AddAttachmentsButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-2.5! shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700" />
<PromptInputActionMenu>
<ModeHoverGuide
mode={
@@ -517,7 +541,7 @@ export function InputBox({
: "flash"
}
>
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white">
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<div>
{context.mode === "flash" && <ZapIcon className="size-3" />}
{context.mode === "thinking" && (
@@ -679,7 +703,7 @@ export function InputBox({
</PromptInputActionMenu>
{supportReasoningEffort && context.mode !== "flash" && (
<PromptInputActionMenu>
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white">
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<div className="text-xs font-normal">
{t.inputBox.reasoningEffort}:
{context.reasoning_effort === "minimal" &&
@@ -800,7 +824,7 @@ export function InputBox({
onOpenChange={setModelDialogOpen}
>
<ModelSelectorTrigger asChild>
<PromptInputButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3 shadow-none hover:bg-white">
<PromptInputButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3 shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<div className="flex min-w-0 flex-col items-start text-left">
<ModelSelectorName className="text-xs font-normal">
{selectedModel?.display_name}
@@ -834,7 +858,7 @@ export function InputBox({
</ModelSelectorContent>
</ModelSelector>
<PromptInputSubmit
className="rounded-full border-[#e7d9c7] bg-white shadow-[0_10px_24px_-18px_rgba(120,83,42,0.34)]"
className="rounded-full border-[#e7d9c7] bg-white shadow-[0_10px_24px_-18px_rgba(120,83,42,0.34)] dark:bg-blue-500 dark:border-blue-500 dark:text-white dark:hover:bg-blue-400 dark:shadow-none"
disabled={disabled}
variant="outline"
status={status}
@@ -842,16 +866,10 @@ export function InputBox({
</PromptInputTools>
</PromptInputFooter>
{!isNewThread && (
<div className="absolute right-0 -bottom-[17px] left-0 z-0 h-4 bg-[#fffdfa]"></div>
<div className="absolute right-0 -bottom-[17px] left-0 z-0 h-4 bg-[#fffdfa] dark:bg-background"></div>
)}
</PromptInput>
{isNewThread && searchParams.get("mode") !== "skill" && (
<div className="flex items-center justify-center pt-2">
<SuggestionList />
</div>
)}
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
<DialogContent>
<DialogHeader>
@@ -68,7 +68,7 @@ export function MessageList({
<Conversation
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">
<ConversationContent className="mx-auto w-full max-w-(--container-width-lg) gap-8 pt-12">
{groupMessages(messages, (group) => {
if (group.type === "human" || group.type === "assistant") {
return group.messages.map((msg) => {
@@ -454,6 +454,7 @@ function ClarificationCard({
const payload = getClarificationPayload(message);
const [customAnswer, setCustomAnswer] = useState("");
const [submitting, setSubmitting] = useState(false);
const [submitted, setSubmitted] = useState(false);
const options = useMemo(
() =>
@@ -473,16 +474,17 @@ function ClarificationCard({
);
}
const disabled = isLoading || submitting || !onSubmit;
const disabled = false;
async function submitAnswer(answer: string) {
const trimmed = answer.trim();
if (!trimmed || disabled) {
if (!trimmed) {
return;
}
setSubmitting(true);
try {
await onSubmit?.(trimmed);
setSubmitted(true);
} finally {
setSubmitting(false);
}
@@ -24,7 +24,7 @@ function SkeletonBar({
export function MessageListSkeleton() {
let index = 0;
return (
<div className="flex w-full max-w-(--container-width-md) flex-col gap-12 p-8 pt-16">
<div className="flex w-full max-w-(--container-width-lg) flex-col gap-12 p-8 pt-16">
<div
role="human-message"
className="flex w-[50%] flex-col items-end gap-2 self-end"
@@ -50,23 +50,23 @@ export function Welcome({
</div>
{searchParams.get("mode") === "skill" ? (
<div className="text-muted-foreground text-sm">
{t.welcome.createYourOwnSkillDescription.includes("\n") ? (
{/* {t.welcome.createYourOwnSkillDescription.includes("\n") ? (
<pre className="font-sans whitespace-pre">
{t.welcome.createYourOwnSkillDescription}
</pre>
) : (
<p>{t.welcome.createYourOwnSkillDescription}</p>
)}
)} */}
</div>
) : (
<div className="text-muted-foreground text-sm">
{t.welcome.description.includes("\n") ? (
{/* {t.welcome.description.includes("\n") ? (
<pre className="font-sans whitespace-pre">
{t.welcome.description}
</pre>
) : (
<p>{t.welcome.description}</p>
)}
)} */}
</div>
)}
</div>
@@ -1,6 +1,6 @@
"use client";
import { ChevronsUpDown, Settings2Icon, SettingsIcon } from "lucide-react";
import { ChevronsUpDown, MessageSquarePlusIcon, Settings2Icon, SettingsIcon } from "lucide-react";
import { useEffect, useState } from "react";
import {
@@ -17,9 +17,12 @@ import {
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import { getStoredAuth } from "@/core/auth";
import { useI18n } from "@/core/i18n/hooks";
import { NotificationBell } from "./notification-bell";
import { useNavigate } from "react-router-dom";
import { SettingsDialog } from "./settings";
function NavMenuButtonContent({
@@ -43,9 +46,11 @@ function NavMenuButtonContent({
}
export function WorkspaceNavMenu() {
const navigate = useNavigate();
const isAdmin = getStoredAuth()?.system_role === "admin";
const [settingsOpen, setSettingsOpen] = useState(false);
const [settingsDefaultSection, setSettingsDefaultSection] = useState<
"appearance" | "memory" | "tools" | "skills" | "notification" | "about"
"appearance" | "memory" | "tools" | "notification" | "about"
>("appearance");
const [mounted, setMounted] = useState(false);
const { open: isSidebarOpen } = useSidebar();
@@ -92,6 +97,16 @@ export function WorkspaceNavMenu() {
<Settings2Icon />
{t.common.settings}
</DropdownMenuItem>
{isAdmin && (
<DropdownMenuItem
onClick={() =>
navigate("/page/workspace/settings/recommended-questions")
}
>
<MessageSquarePlusIcon />
推荐问题配置
</DropdownMenuItem>
)}
</DropdownMenuGroup>
<DropdownMenuSeparator />
</DropdownMenuContent>