feat: add Claw skill search
This commit is contained in:
parent
d467694aa7
commit
233c7b3308
@ -25,7 +25,7 @@ import {
|
|||||||
type UiSurfaceSnapshot,
|
type UiSurfaceSnapshot,
|
||||||
} from "@/lib/ui-surface-protocol";
|
} from "@/lib/ui-surface-protocol";
|
||||||
import useSWR, { useSWRConfig } from "swr";
|
import useSWR, { useSWRConfig } from "swr";
|
||||||
import { Loader2, ImagePlus, X, Plus, History, LayoutGrid, ArrowRightToLine, RefreshCw, Globe, ArrowUp, Video, Sparkles, Check, ChevronDown, ChevronUp, AlertCircle, CheckCircle2, Brain, ThumbsUp, ThumbsDown, Eye, Trash2, Download, MessageCircle, Clapperboard, Bot, FileSpreadsheet, Paperclip, Copy, Square, GripVertical } from "lucide-react";
|
import { Loader2, ImagePlus, X, Plus, History, LayoutGrid, ArrowRightToLine, RefreshCw, Globe, ArrowUp, Video, Sparkles, Check, ChevronDown, ChevronUp, AlertCircle, CheckCircle2, Brain, ThumbsUp, ThumbsDown, Eye, Trash2, Download, MessageCircle, Clapperboard, Bot, FileSpreadsheet, Paperclip, Copy, Square, Search, GripVertical } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
@ -2656,6 +2656,7 @@ export function CanvasChatPanel({
|
|||||||
const [isComposerModeMenuOpen, setIsComposerModeMenuOpen] = useState(false);
|
const [isComposerModeMenuOpen, setIsComposerModeMenuOpen] = useState(false);
|
||||||
const [selectedSkillTagId, setSelectedSkillTagId] = useState<string | null>(null);
|
const [selectedSkillTagId, setSelectedSkillTagId] = useState<string | null>(null);
|
||||||
const [areCanvasSkillTagsExpanded, setAreCanvasSkillTagsExpanded] = useState(false);
|
const [areCanvasSkillTagsExpanded, setAreCanvasSkillTagsExpanded] = useState(false);
|
||||||
|
const [canvasSkillTagSearchQuery, setCanvasSkillTagSearchQuery] = useState("");
|
||||||
const [draggingSkillTagId, setDraggingSkillTagId] = useState<string | null>(null);
|
const [draggingSkillTagId, setDraggingSkillTagId] = useState<string | null>(null);
|
||||||
const [canvasSkillTagOrderOverride, setCanvasSkillTagOrderOverride] = useState<string[] | null>(
|
const [canvasSkillTagOrderOverride, setCanvasSkillTagOrderOverride] = useState<string[] | null>(
|
||||||
null
|
null
|
||||||
@ -4817,6 +4818,25 @@ export function CanvasChatPanel({
|
|||||||
: baseCanvasSkillTags,
|
: baseCanvasSkillTags,
|
||||||
[baseCanvasSkillTags, canvasSkillTagOrderOverride]
|
[baseCanvasSkillTags, canvasSkillTagOrderOverride]
|
||||||
);
|
);
|
||||||
|
const normalizedCanvasSkillTagSearchQuery = canvasSkillTagSearchQuery
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase();
|
||||||
|
const filteredCanvasSkillTags = useMemo(() => {
|
||||||
|
if (!normalizedCanvasSkillTagSearchQuery) {
|
||||||
|
return canvasSkillTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keywords = normalizedCanvasSkillTagSearchQuery.split(/\s+/).filter(Boolean);
|
||||||
|
return canvasSkillTags.filter((tag) => {
|
||||||
|
const searchableText = [tag.name, tag.prompt, tag.placeholder]
|
||||||
|
.map((value) => String(value || "").trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n")
|
||||||
|
.toLocaleLowerCase();
|
||||||
|
return keywords.every((keyword) => searchableText.includes(keyword));
|
||||||
|
});
|
||||||
|
}, [canvasSkillTags, normalizedCanvasSkillTagSearchQuery]);
|
||||||
|
const isCanvasSkillTagSearching = normalizedCanvasSkillTagSearchQuery.length > 0;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
latestCanvasSkillTagsRef.current = canvasSkillTags;
|
latestCanvasSkillTagsRef.current = canvasSkillTags;
|
||||||
}, [canvasSkillTags]);
|
}, [canvasSkillTags]);
|
||||||
@ -4869,7 +4889,8 @@ export function CanvasChatPanel({
|
|||||||
() => canvasSkillTags.find((tag) => tag.id === selectedSkillTagId) || null,
|
() => canvasSkillTags.find((tag) => tag.id === selectedSkillTagId) || null,
|
||||||
[canvasSkillTags, selectedSkillTagId]
|
[canvasSkillTags, selectedSkillTagId]
|
||||||
);
|
);
|
||||||
const shouldShowCanvasSkillTagsToggle = canvasSkillTags.length > 4;
|
const shouldShowCanvasSkillTagsToggle =
|
||||||
|
!isCanvasSkillTagSearching && canvasSkillTags.length > 4;
|
||||||
const activeChatSession = useMemo(
|
const activeChatSession = useMemo(
|
||||||
() => chatSessions.find((session) => session.id === activeChatId) || null,
|
() => chatSessions.find((session) => session.id === activeChatId) || null,
|
||||||
[activeChatId, chatSessions]
|
[activeChatId, chatSessions]
|
||||||
@ -6169,6 +6190,7 @@ export function CanvasChatPanel({
|
|||||||
promptApplied: false,
|
promptApplied: false,
|
||||||
});
|
});
|
||||||
pendingSelectedClawdSkillTagRef.current = tag;
|
pendingSelectedClawdSkillTagRef.current = tag;
|
||||||
|
setCanvasSkillTagSearchQuery("");
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
setSelectedSkillTagId(tag.id);
|
setSelectedSkillTagId(tag.id);
|
||||||
const editor = textareaRef.current as HTMLDivElement | null;
|
const editor = textareaRef.current as HTMLDivElement | null;
|
||||||
@ -14382,15 +14404,49 @@ export function CanvasChatPanel({
|
|||||||
<h3 className="text-[14px] font-semibold leading-none text-[#1f2328] dark:text-zinc-100">
|
<h3 className="text-[14px] font-semibold leading-none text-[#1f2328] dark:text-zinc-100">
|
||||||
{canvasSkillTagsTitle}
|
{canvasSkillTagsTitle}
|
||||||
</h3>
|
</h3>
|
||||||
|
{canvasSkillTags.length > 4 ? (
|
||||||
|
<div className="mx-auto mt-5 flex h-9 w-full max-w-[360px] items-center gap-2 rounded-lg border border-[#e5e7eb] bg-[#fafbfc] px-3 text-left transition-colors focus-within:border-[#aeb6c5] focus-within:bg-white dark:border-white/10 dark:bg-white/5 dark:focus-within:border-white/25 dark:focus-within:bg-white/[0.07]">
|
||||||
|
<Search className="h-4 w-4 shrink-0 text-[#9299a6] dark:text-zinc-500" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={canvasSkillTagSearchQuery}
|
||||||
|
onChange={(event) =>
|
||||||
|
setCanvasSkillTagSearchQuery(event.target.value)
|
||||||
|
}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
setCanvasSkillTagSearchQuery("");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="搜索快捷技能"
|
||||||
|
aria-label="搜索快捷技能"
|
||||||
|
autoComplete="off"
|
||||||
|
enterKeyHint="search"
|
||||||
|
spellCheck={false}
|
||||||
|
className="h-full min-w-0 flex-1 bg-transparent text-[13px] text-[#1f2328] outline-none placeholder:text-[#9aa1ad] dark:text-zinc-100 dark:placeholder:text-zinc-500"
|
||||||
|
/>
|
||||||
|
{canvasSkillTagSearchQuery ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setCanvasSkillTagSearchQuery("")}
|
||||||
|
className="inline-flex h-6 w-6 shrink-0 items-center justify-center text-[#9299a6] transition-colors hover:text-[#1f2328] dark:text-zinc-500 dark:hover:text-zinc-100"
|
||||||
|
title="清空搜索"
|
||||||
|
aria-label="清空快捷技能搜索"
|
||||||
|
>
|
||||||
|
<X className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-4 flex flex-wrap justify-center gap-1.5 transition-[max-height] duration-150 ease-out",
|
"mt-4 flex flex-wrap justify-center gap-1.5 transition-[max-height] duration-150 ease-out",
|
||||||
areCanvasSkillTagsExpanded
|
isCanvasSkillTagSearching || areCanvasSkillTagsExpanded
|
||||||
? "max-h-none overflow-visible"
|
? "max-h-none overflow-visible"
|
||||||
: "max-h-[76px] overflow-hidden"
|
: "max-h-[76px] overflow-hidden"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{canvasSkillTags.map((tag) => {
|
{filteredCanvasSkillTags.map((tag) => {
|
||||||
const isActive = selectedSkillTag?.id === tag.id;
|
const isActive = selectedSkillTag?.id === tag.id;
|
||||||
const isDragging = draggingSkillTagId === tag.id;
|
const isDragging = draggingSkillTagId === tag.id;
|
||||||
const TagIcon = getCanvasSkillIconComponent(tag.iconName);
|
const TagIcon = getCanvasSkillIconComponent(tag.iconName);
|
||||||
@ -14398,7 +14454,7 @@ export function CanvasChatPanel({
|
|||||||
<button
|
<button
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
type="button"
|
type="button"
|
||||||
draggable={canvasSkillTags.length > 1}
|
draggable={!isCanvasSkillTagSearching && canvasSkillTags.length > 1}
|
||||||
onDragStart={(event) =>
|
onDragStart={(event) =>
|
||||||
handleCanvasSkillTagDragStart(event, tag.id)
|
handleCanvasSkillTagDragStart(event, tag.id)
|
||||||
}
|
}
|
||||||
@ -14412,13 +14468,20 @@ export function CanvasChatPanel({
|
|||||||
onDragEnd={handleCanvasSkillTagDragEnd}
|
onDragEnd={handleCanvasSkillTagDragEnd}
|
||||||
onClick={() => handleSelectCanvasSkillTag(tag)}
|
onClick={() => handleSelectCanvasSkillTag(tag)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-8 max-w-full cursor-grab items-center justify-center gap-1.5 rounded-full border px-3 text-[12px] font-medium leading-none transition-colors duration-150 active:cursor-grabbing",
|
"inline-flex h-8 max-w-full items-center justify-center gap-1.5 rounded-full border px-3 text-[12px] font-medium leading-none transition-colors duration-150",
|
||||||
|
isCanvasSkillTagSearching
|
||||||
|
? "cursor-pointer"
|
||||||
|
: "cursor-grab active:cursor-grabbing",
|
||||||
isActive
|
isActive
|
||||||
? "border-[#d8dce4] bg-[#f7f8fa] text-[#1f2328] dark:border-blue-400/40 dark:bg-blue-500/15 dark:text-blue-200"
|
? "border-[#d8dce4] bg-[#f7f8fa] text-[#1f2328] dark:border-blue-400/40 dark:bg-blue-500/15 dark:text-blue-200"
|
||||||
: "border-[#e5e7eb] bg-white text-[#1f2328] hover:border-[#d6dae3] hover:bg-[#fafbfc] dark:border-white/10 dark:bg-white/5 dark:text-zinc-200 dark:hover:border-white/20 dark:hover:bg-white/10",
|
: "border-[#e5e7eb] bg-white text-[#1f2328] hover:border-[#d6dae3] hover:bg-[#fafbfc] dark:border-white/10 dark:bg-white/5 dark:text-zinc-200 dark:hover:border-white/20 dark:hover:bg-white/10",
|
||||||
isDragging && "scale-95 opacity-45"
|
isDragging && "scale-95 opacity-45"
|
||||||
)}
|
)}
|
||||||
title="点击使用,拖动调整顺序"
|
title={
|
||||||
|
isCanvasSkillTagSearching
|
||||||
|
? "点击使用快捷技能"
|
||||||
|
: "点击使用,拖动调整顺序"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{tag.iconUrl ? (
|
{tag.iconUrl ? (
|
||||||
<img
|
<img
|
||||||
@ -14440,6 +14503,11 @@ export function CanvasChatPanel({
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{isCanvasSkillTagSearching && filteredCanvasSkillTags.length === 0 ? (
|
||||||
|
<p className="w-full py-2 text-center text-[13px] text-[#9299a6] dark:text-zinc-500">
|
||||||
|
未找到匹配的快捷技能
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{shouldShowCanvasSkillTagsToggle ? (
|
{shouldShowCanvasSkillTagsToggle ? (
|
||||||
<button
|
<button
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user