From 233c7b3308367e6640c328693fa5fe2ea116d359 Mon Sep 17 00:00:00 2001 From: 134 Date: Tue, 4 Aug 2026 13:49:10 +0800 Subject: [PATCH] feat: add Claw skill search --- components/canvas/CanvasChatPanel.tsx | 82 ++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index f949beb3c..2520c8975 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -25,7 +25,7 @@ import { type UiSurfaceSnapshot, } from "@/lib/ui-surface-protocol"; 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 { Collapsible, @@ -2656,6 +2656,7 @@ export function CanvasChatPanel({ const [isComposerModeMenuOpen, setIsComposerModeMenuOpen] = useState(false); const [selectedSkillTagId, setSelectedSkillTagId] = useState(null); const [areCanvasSkillTagsExpanded, setAreCanvasSkillTagsExpanded] = useState(false); + const [canvasSkillTagSearchQuery, setCanvasSkillTagSearchQuery] = useState(""); const [draggingSkillTagId, setDraggingSkillTagId] = useState(null); const [canvasSkillTagOrderOverride, setCanvasSkillTagOrderOverride] = useState( null @@ -4817,6 +4818,25 @@ export function CanvasChatPanel({ : baseCanvasSkillTags, [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(() => { latestCanvasSkillTagsRef.current = canvasSkillTags; }, [canvasSkillTags]); @@ -4869,7 +4889,8 @@ export function CanvasChatPanel({ () => canvasSkillTags.find((tag) => tag.id === selectedSkillTagId) || null, [canvasSkillTags, selectedSkillTagId] ); - const shouldShowCanvasSkillTagsToggle = canvasSkillTags.length > 4; + const shouldShowCanvasSkillTagsToggle = + !isCanvasSkillTagSearching && canvasSkillTags.length > 4; const activeChatSession = useMemo( () => chatSessions.find((session) => session.id === activeChatId) || null, [activeChatId, chatSessions] @@ -6169,6 +6190,7 @@ export function CanvasChatPanel({ promptApplied: false, }); pendingSelectedClawdSkillTagRef.current = tag; + setCanvasSkillTagSearchQuery(""); window.setTimeout(() => { setSelectedSkillTagId(tag.id); const editor = textareaRef.current as HTMLDivElement | null; @@ -14382,15 +14404,49 @@ export function CanvasChatPanel({

{canvasSkillTagsTitle}

+ {canvasSkillTags.length > 4 ? ( +
+ + + 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 ? ( + + ) : null} +
+ ) : null}
- {canvasSkillTags.map((tag) => { + {filteredCanvasSkillTags.map((tag) => { const isActive = selectedSkillTag?.id === tag.id; const isDragging = draggingSkillTagId === tag.id; const TagIcon = getCanvasSkillIconComponent(tag.iconName); @@ -14398,7 +14454,7 @@ export function CanvasChatPanel({
{shouldShowCanvasSkillTagsToggle ? (