feat(tour):漫游导航
This commit is contained in:
parent
f0d93ab342
commit
eb45bba7ff
|
|
@ -58,6 +58,7 @@
|
||||||
"@uiw/react-codemirror": "^4.25.4",
|
"@uiw/react-codemirror": "^4.25.4",
|
||||||
"@xyflow/react": "^12.10.0",
|
"@xyflow/react": "^12.10.0",
|
||||||
"ai": "^6.0.33",
|
"ai": "^6.0.33",
|
||||||
|
"antd": "^6.3.6",
|
||||||
"best-effort-json-parser": "^1.2.1",
|
"best-effort-json-parser": "^1.2.1",
|
||||||
"better-auth": "^1.3",
|
"better-auth": "^1.3",
|
||||||
"canvas-confetti": "^1.9.4",
|
"canvas-confetti": "^1.9.4",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1064,7 +1064,7 @@ export const PromptInputTools = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: PromptInputToolsProps) => (
|
}: PromptInputToolsProps) => (
|
||||||
<div className={cn("flex items-center gap-1", className)} {...props} />
|
<div className={cn("flex items-center h-full gap-1", className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PromptInputButtonProps = ComponentProps<typeof InputGroupButton>;
|
export type PromptInputButtonProps = ComponentProps<typeof InputGroupButton>;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputGroupAddonVariants = cva(
|
const inputGroupAddonVariants = cva(
|
||||||
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
|
"text-muted-foreground flex h-[58px] cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
align: {
|
align: {
|
||||||
|
|
@ -46,9 +46,9 @@ const inputGroupAddonVariants = cva(
|
||||||
"inline-end":
|
"inline-end":
|
||||||
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
||||||
"block-start":
|
"block-start":
|
||||||
"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
|
"order-first w-full justify-start px-3 pt-5 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
|
||||||
"block-end":
|
"block-end":
|
||||||
"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
|
"order-last w-full justify-start px-3 py-0 pb-5 group-has-[>input]/input-group:pb-2.5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { ChatStatus } from "ai";
|
import type { ChatStatus } from "ai";
|
||||||
|
import { Tour } from "antd";
|
||||||
import {
|
import {
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
GraduationCapIcon,
|
GraduationCapIcon,
|
||||||
|
|
@ -17,6 +18,7 @@ import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
|
@ -25,6 +27,7 @@ import {
|
||||||
type ChangeEvent,
|
type ChangeEvent,
|
||||||
type KeyboardEvent,
|
type KeyboardEvent,
|
||||||
type ComponentProps,
|
type ComponentProps,
|
||||||
|
type RefObject,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
|
@ -99,6 +102,25 @@ import { Tooltip } from "./tooltip";
|
||||||
|
|
||||||
|
|
||||||
const MAX_REFERENCES_PER_MESSAGE = 10;
|
const MAX_REFERENCES_PER_MESSAGE = 10;
|
||||||
|
const INPUT_TOOLS_TOUR_SEEN_KEY = "workspace.input_tools_tour_seen.v1";
|
||||||
|
|
||||||
|
type WorkspaceToolButtonProps = ComponentProps<typeof PromptInputButton>;
|
||||||
|
|
||||||
|
function WorkspaceToolButton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: WorkspaceToolButtonProps) {
|
||||||
|
return (
|
||||||
|
<PromptInputButton
|
||||||
|
className={cn(
|
||||||
|
// border border-[rgba(0,0,0,0.08)]
|
||||||
|
"group h-full p-[10px]! rounded-[10px] hover:bg-[#EAE2F5] hover:text-[#8E47F0]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type MentionCandidate = {
|
type MentionCandidate = {
|
||||||
key: string;
|
key: string;
|
||||||
|
|
@ -215,6 +237,10 @@ export function InputBox({
|
||||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const mentionTriggerRef = useRef<HTMLButtonElement | null>(null);
|
const mentionTriggerRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
const historyButtonTourRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const attachmentsButtonTourRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const skillButtonTourRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const suggestionListTourRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [followups, setFollowups] = useState<string[]>([]);
|
const [followups, setFollowups] = useState<string[]>([]);
|
||||||
const [followupsHidden, setFollowupsHidden] = useState(false);
|
const [followupsHidden, setFollowupsHidden] = useState(false);
|
||||||
const [followupsLoading, setFollowupsLoading] = useState(false);
|
const [followupsLoading, setFollowupsLoading] = useState(false);
|
||||||
|
|
@ -231,11 +257,97 @@ export function InputBox({
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
const [isInputToolsTourOpen, setIsInputToolsTourOpen] = useState(false);
|
||||||
|
const [isInputToolsTourReady, setIsInputToolsTourReady] = useState(false);
|
||||||
const { data: uploadedFilesData } = useUploadedFiles(threadIdFromProps);
|
const { data: uploadedFilesData } = useUploadedFiles(threadIdFromProps);
|
||||||
|
|
||||||
// isNewThread 时禁用收缩,始终保持展开(除非已提交消息)
|
// isNewThread 时禁用收缩,始终保持展开(除非已提交消息)
|
||||||
const effectiveIsFocused =
|
const effectiveIsFocused =
|
||||||
((showWelcomeStyle ?? false) && !hasSubmitted) || isFocused;
|
((showWelcomeStyle ?? false) && !hasSubmitted) || isFocused;
|
||||||
|
const shouldShowSuggestionList =
|
||||||
|
showWelcomeStyle && !hasSubmitted && searchParams.get("mode") !== "skill";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showWelcomeStyle || hasSubmitted) {
|
||||||
|
setIsInputToolsTourReady(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const frameId = window.requestAnimationFrame(() => {
|
||||||
|
setIsInputToolsTourReady(
|
||||||
|
Boolean(
|
||||||
|
historyButtonTourRef.current &&
|
||||||
|
attachmentsButtonTourRef.current &&
|
||||||
|
skillButtonTourRef.current &&
|
||||||
|
(!shouldShowSuggestionList || suggestionListTourRef.current),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return () => window.cancelAnimationFrame(frameId);
|
||||||
|
}, [
|
||||||
|
showWelcomeStyle,
|
||||||
|
hasSubmitted,
|
||||||
|
shouldShowSuggestionList,
|
||||||
|
iframeSkill.isBootstrapping,
|
||||||
|
iframeSkill.selectedSkills.length,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showWelcomeStyle || hasSubmitted || !isInputToolsTourReady) {
|
||||||
|
setIsInputToolsTourOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hasSeenTour = window.localStorage.getItem(INPUT_TOOLS_TOUR_SEEN_KEY);
|
||||||
|
if (!hasSeenTour) {
|
||||||
|
setIsInputToolsTourOpen(true);
|
||||||
|
}
|
||||||
|
}, [showWelcomeStyle, hasSubmitted, isInputToolsTourReady]);
|
||||||
|
|
||||||
|
const closeInputToolsTour = useCallback(() => {
|
||||||
|
window.localStorage.setItem(INPUT_TOOLS_TOUR_SEEN_KEY, "1");
|
||||||
|
setIsInputToolsTourOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const inputToolsTourSteps = useMemo(() => {
|
||||||
|
const baseSteps = [
|
||||||
|
{
|
||||||
|
title: "查看历史",
|
||||||
|
description: "点击这里,可以查看历史会话与文档。",
|
||||||
|
target: () => historyButtonTourRef.current ?? document.body,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "上传附件",
|
||||||
|
description: "点击这里,上传参考文档或拟处理的文档。",
|
||||||
|
target: () => attachmentsButtonTourRef.current ?? document.body,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "选择 Skill",
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
点击这里,从“我的skill”中选择要使用的skill。
|
||||||
|
<br />
|
||||||
|
在广场中选择skill,在详情页选择“去使用”,也可选中skill。
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
target: () => skillButtonTourRef.current ?? document.body,
|
||||||
|
},
|
||||||
|
...(shouldShowSuggestionList
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: "试试我吧",
|
||||||
|
target: () => suggestionListTourRef.current ?? document.body,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
|
||||||
|
return baseSteps.map((step, index) => ({
|
||||||
|
...step,
|
||||||
|
prevButtonProps: { children: "上一步" },
|
||||||
|
nextButtonProps: {
|
||||||
|
children: index === baseSteps.length - 1 ? "完成" : "下一步",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}, [shouldShowSuggestionList]);
|
||||||
|
|
||||||
// 点击外部区域时收起输入框
|
// 点击外部区域时收起输入框
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -615,6 +727,21 @@ export function InputBox({
|
||||||
}}
|
}}
|
||||||
className="relative w-full"
|
className="relative w-full"
|
||||||
>
|
>
|
||||||
|
<Tour
|
||||||
|
open={isInputToolsTourOpen}
|
||||||
|
onClose={closeInputToolsTour}
|
||||||
|
onFinish={closeInputToolsTour}
|
||||||
|
gap={
|
||||||
|
{ offset: 4 , radius: 2 }
|
||||||
|
}
|
||||||
|
mask={{
|
||||||
|
style: {
|
||||||
|
boxShadow: 'inset 0 0 15px #333',
|
||||||
|
},
|
||||||
|
color: 'rgba(255,255,255, .8)',
|
||||||
|
}}
|
||||||
|
steps={inputToolsTourSteps}
|
||||||
|
/>
|
||||||
<AttachmentPreviewBar
|
<AttachmentPreviewBar
|
||||||
references={references}
|
references={references}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
|
|
@ -784,7 +911,7 @@ export function InputBox({
|
||||||
"pointer-events-none invisible h-[0px] translate-y-2 p-[0px] opacity-0",
|
"pointer-events-none invisible h-[0px] translate-y-2 p-[0px] opacity-0",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<PromptInputTools className="min-w-0 flex-1 gap-[20px]">
|
<PromptInputTools className="min-w-0 w-full overflow-hidden gap-[20px]">
|
||||||
{/* TODO: Add more connectors here
|
{/* TODO: Add more connectors here
|
||||||
<PromptInputActionMenu>
|
<PromptInputActionMenu>
|
||||||
<PromptInputActionMenuTrigger className="px-2!" />
|
<PromptInputActionMenuTrigger className="px-2!" />
|
||||||
|
|
@ -795,20 +922,26 @@ export function InputBox({
|
||||||
</PromptInputActionMenuContent>
|
</PromptInputActionMenuContent>
|
||||||
</PromptInputActionMenu> */}
|
</PromptInputActionMenu> */}
|
||||||
{showWelcomeStyle && (
|
{showWelcomeStyle && (
|
||||||
|
<div ref={historyButtonTourRef} className="shrink-0 h-full">
|
||||||
<HistoryButton
|
<HistoryButton
|
||||||
className="px-2!"
|
|
||||||
router={router}
|
router={router}
|
||||||
threadId={threadIdFromProps}
|
threadId={threadIdFromProps}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<AddAttachmentsButton className="px-2!" />
|
<div ref={attachmentsButtonTourRef} className="shrink-0 h-full">
|
||||||
|
<AddAttachmentsButton />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 grow basis-0 h-full">
|
||||||
<IframeSkillDialogButton
|
<IframeSkillDialogButton
|
||||||
className="px-2!"
|
skillButtonRef={skillButtonTourRef}
|
||||||
selectedSkills={iframeSkill.selectedSkills}
|
selectedSkills={iframeSkill.selectedSkills}
|
||||||
isBootstrapping={iframeSkill.isBootstrapping}
|
isBootstrapping={iframeSkill.isBootstrapping}
|
||||||
openSkillDialog={iframeSkill.openSkillDialog}
|
openSkillDialog={iframeSkill.openSkillDialog}
|
||||||
clearSkill={iframeSkill.clearSkill}
|
clearSkill={iframeSkill.clearSkill}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
{/* <div className="h-[40px] w-[140px] shrink-0" aria-hidden="true" /> */}
|
||||||
|
|
||||||
{/* 参考 kexue 版本隐藏运行模式切换按钮 */}
|
{/* 参考 kexue 版本隐藏运行模式切换按钮 */}
|
||||||
</PromptInputTools>
|
</PromptInputTools>
|
||||||
|
|
@ -845,7 +978,7 @@ export function InputBox({
|
||||||
</ModelSelector> */}
|
</ModelSelector> */}
|
||||||
<PromptInputTools>
|
<PromptInputTools>
|
||||||
{/* 占位符 */}
|
{/* 占位符 */}
|
||||||
<div className="w-[150px]"></div>
|
<div className="w-[150px] h-[40px]"></div>
|
||||||
</PromptInputTools>
|
</PromptInputTools>
|
||||||
</PromptInputFooter>
|
</PromptInputFooter>
|
||||||
<PromptInputSubmit
|
<PromptInputSubmit
|
||||||
|
|
@ -856,10 +989,9 @@ export function InputBox({
|
||||||
/>
|
/>
|
||||||
</PromptInput>
|
</PromptInput>
|
||||||
|
|
||||||
{showWelcomeStyle &&
|
{shouldShowSuggestionList && (
|
||||||
!hasSubmitted &&
|
|
||||||
searchParams.get("mode") !== "skill" && (
|
|
||||||
<SuggestionListContainer
|
<SuggestionListContainer
|
||||||
|
ref={suggestionListTourRef}
|
||||||
bootstrapAndLockSkills={iframeSkill.bootstrapAndLockSkills}
|
bootstrapAndLockSkills={iframeSkill.bootstrapAndLockSkills}
|
||||||
isBootstrapping={iframeSkill.isBootstrapping}
|
isBootstrapping={iframeSkill.isBootstrapping}
|
||||||
/>
|
/>
|
||||||
|
|
@ -926,25 +1058,29 @@ export function InputBox({
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuggestionList 容器
|
// SuggestionList 容器
|
||||||
function SuggestionListContainer({
|
const SuggestionListContainer = forwardRef<HTMLDivElement, {
|
||||||
bootstrapAndLockSkills,
|
|
||||||
isBootstrapping,
|
|
||||||
}: {
|
|
||||||
bootstrapAndLockSkills: (params: {
|
bootstrapAndLockSkills: (params: {
|
||||||
selectedSkills: SelectedSkillPayloadItem[];
|
selectedSkills: SelectedSkillPayloadItem[];
|
||||||
title: string;
|
title: string;
|
||||||
}) => Promise<boolean>;
|
}) => Promise<boolean>;
|
||||||
isBootstrapping: boolean;
|
isBootstrapping: boolean;
|
||||||
}) {
|
}>(
|
||||||
|
function SuggestionListContainer(
|
||||||
|
{ bootstrapAndLockSkills, isBootstrapping },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<div className="absolute right-0 bottom-0 left-0 z-0 flex translate-y-full items-center justify-center pt-4">
|
<div className="absolute right-0 bottom-0 left-0 z-0 flex translate-y-full items-center justify-center pt-4">
|
||||||
|
<div ref={ref} className="w-fit">
|
||||||
<SuggestionList
|
<SuggestionList
|
||||||
bootstrapAndLockSkills={bootstrapAndLockSkills}
|
bootstrapAndLockSkills={bootstrapAndLockSkills}
|
||||||
isBootstrapping={isBootstrapping}
|
isBootstrapping={isBootstrapping}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// 快速选择skillbutton
|
// 快速选择skillbutton
|
||||||
function SuggestionList({
|
function SuggestionList({
|
||||||
|
|
@ -1046,8 +1182,8 @@ function AddAttachmentsButton({ className }: { className?: string }) {
|
||||||
const attachments = usePromptInputAttachments();
|
const attachments = usePromptInputAttachments();
|
||||||
return (
|
return (
|
||||||
<Tooltip content={t.inputBox.addAttachments}>
|
<Tooltip content={t.inputBox.addAttachments}>
|
||||||
<PromptInputButton
|
<WorkspaceToolButton
|
||||||
className={cn("group px-2! hover:bg-[#EAE2F5]", className)}
|
className={className}
|
||||||
onClick={() => attachments.openFileDialog()}
|
onClick={() => attachments.openFileDialog()}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|
@ -1067,7 +1203,7 @@ function AddAttachmentsButton({ className }: { className?: string }) {
|
||||||
stroke="#150033"
|
stroke="#150033"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</PromptInputButton>
|
</WorkspaceToolButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1084,8 +1220,8 @@ function HistoryButton({
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
return (
|
return (
|
||||||
<Tooltip content={t.inputBox.history}>
|
<Tooltip content={t.inputBox.history}>
|
||||||
<PromptInputButton
|
<WorkspaceToolButton
|
||||||
className={cn("group px-2! hover:bg-[#EAE2F5]", className)}
|
className={className}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.replace(`/workspace/chats/${threadId}?is_chatting=true`)
|
router.replace(`/workspace/chats/${threadId}?is_chatting=true`)
|
||||||
}
|
}
|
||||||
|
|
@ -1111,19 +1247,21 @@ function HistoryButton({
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</PromptInputButton>
|
</WorkspaceToolButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// 启动iframeSkillDialog
|
// 启动iframeSkillDialog
|
||||||
function IframeSkillDialogButton({
|
function IframeSkillDialogButton({
|
||||||
className,
|
className,
|
||||||
|
skillButtonRef,
|
||||||
selectedSkills,
|
selectedSkills,
|
||||||
isBootstrapping,
|
isBootstrapping,
|
||||||
openSkillDialog,
|
openSkillDialog,
|
||||||
clearSkill,
|
clearSkill,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
skillButtonRef?: RefObject<HTMLDivElement | null>;
|
||||||
selectedSkills: Array<{ skill_id: string; title: string }>;
|
selectedSkills: Array<{ skill_id: string; title: string }>;
|
||||||
isBootstrapping: boolean;
|
isBootstrapping: boolean;
|
||||||
openSkillDialog: () => void;
|
openSkillDialog: () => void;
|
||||||
|
|
@ -1132,10 +1270,11 @@ function IframeSkillDialogButton({
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
<div className="flex min-w-0 w-full items-center h-full gap-2">
|
||||||
<Tooltip content={t.inputBox.selectSkill}>
|
<Tooltip content={t.inputBox.selectSkill}>
|
||||||
<PromptInputButton
|
<div ref={skillButtonRef} className="shrink-0">
|
||||||
className={cn("group shrink-0 px-2! hover:bg-[#EAE2F5]", className)}
|
<WorkspaceToolButton
|
||||||
|
className={cn("shrink-0", className)}
|
||||||
onClick={openSkillDialog}
|
onClick={openSkillDialog}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|
@ -1149,7 +1288,8 @@ function IframeSkillDialogButton({
|
||||||
stroke="#150033"
|
stroke="#150033"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</PromptInputButton>
|
</WorkspaceToolButton>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{isBootstrapping ? (
|
{isBootstrapping ? (
|
||||||
<Tag className="bg-background text-muted-foreground gap-2 border">
|
<Tag className="bg-background text-muted-foreground gap-2 border">
|
||||||
|
|
@ -1159,7 +1299,7 @@ function IframeSkillDialogButton({
|
||||||
) : null}
|
) : null}
|
||||||
{!isBootstrapping && selectedSkills.length > 0 ? (
|
{!isBootstrapping && selectedSkills.length > 0 ? (
|
||||||
<div
|
<div
|
||||||
className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto overflow-y-hidden whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
className="flex min-w-0 grow basis-0 items-center gap-2 overflow-x-auto overflow-y-hidden whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||||
onWheel={(event) => {
|
onWheel={(event) => {
|
||||||
if (event.deltaY === 0) return;
|
if (event.deltaY === 0) return;
|
||||||
event.currentTarget.scrollLeft += event.deltaY;
|
event.currentTarget.scrollLeft += event.deltaY;
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export const zhCN: Translations = {
|
||||||
// Input Box
|
// Input Box
|
||||||
inputBox: {
|
inputBox: {
|
||||||
placeholder: "可直接对话; 或输入需求并选择skill,完成专业任务;",
|
placeholder: "可直接对话; 或输入需求并选择skill,完成专业任务;",
|
||||||
welcomePlaceholder: "可直接对话; 或输入需求并选择skill,完成专业任务;",
|
welcomePlaceholder: "可直接对话; 或输入需求并选择skill,完成专业任务。",
|
||||||
chatPlaceholder: "“@”可引用文件。",
|
chatPlaceholder: "“@”可引用文件。",
|
||||||
createSkillPrompt:
|
createSkillPrompt:
|
||||||
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
|
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue