feat(frontend): 广场搜索体验优化与切换tab重置筛选

- 智能体广场:切换 我的/广场/私密广场 时清空重置搜索/标签/排序/发布者筛选
- 通用搜索栏(TagSearchBar):新增「搜索」按钮与回车立即触发(跳过防抖)、
  搜索中转圈提示,新增 hideTags / searchPlaceholder 选项
- 定时任务广场:新增客户端搜索(名称/摘要/创建者,兼容脱敏文本)
- agents 分页 hook 暴露 isFetching 以驱动搜索中状态

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-10 09:37:39 +08:00
1 parent 87e0ae683e
commit 8fbbfc8093
4 files changed
+127 -25

No files matched your search

+43 -7
View File
@@ -149,13 +149,16 @@ export default function ScheduledTasksPage() {
const initialTaskId = searchParams.get("task");
const [search, setSearch] = useState("");
const [tagIds, setTagIds] = useState<string[]>([]);
// Plaza (广场) name search is resolved client-side over the fetched published
// list, kept separate from the server-side 我的任务 search above.
const [plazaSearch, setPlazaSearch] = useState("");
// Publisher (创建者) filter for the 我的任务 tab — click a task's 创建者 to keep
// only that creator's tasks. {id} is creator_user_id; {label} the display name.
const [publisher, setPublisher] = useState<{ id: string; label: string } | null>(
null,
);
// Name + tag filtering is resolved server-side via the /api/scheduled-tasks query.
const { data: tasks = [], isLoading } = useScheduledTasks({ search, tagIds });
const { data: tasks = [], isLoading, isFetching } = useScheduledTasks({ search, tagIds });
const { data: deliveryProfile } = useScheduledTaskDeliveryProfile();
const { agents } = useAgents();
const actions = useScheduledTaskActions();
@@ -215,6 +218,24 @@ export default function ScheduledTasksPage() {
: sortedTasks,
[sortedTasks, publisher],
);
// Plaza search: filter the fetched published list client-side. Match against
// both the raw and desensitized forms so typing what's shown on screen (the
// desensitized name) and the underlying text both hit.
const visiblePublished = useMemo(() => {
const q = plazaSearch.trim().toLowerCase();
if (!q) return published;
return published.filter((item) =>
[
item.name,
desensitize(item.name),
item.prompt_summary,
desensitize(item.prompt_summary ?? ""),
item.schedule_text,
displayUsername(item.creator_email),
].some((field) => (field ?? "").toLowerCase().includes(q)),
);
}, [published, plazaSearch]);
const selectedTask = sortedTasks.find((task) => task.task_id === selectedTaskId) ?? null;
const selectedTaskCanEditRuns = !!selectedTask && selectedTask.role !== "subscriber";
const { data: runs = [], isLoading: runsLoading } = useScheduledTaskRuns(selectedTaskId);
@@ -465,6 +486,16 @@ export default function ScheduledTasksPage() {
onSearchChange={setSearch}
tagIds={tagIds}
onTagIdsChange={setTagIds}
searching={isFetching}
/>
)}
{activeTab === "plaza" && (
<TagSearchBar
targetType="scheduled_task"
hideTags
onSearchChange={setPlazaSearch}
searching={publishedLoading}
searchPlaceholder="搜索广场任务(名称 / 摘要 / 创建者)"
/>
)}
{activeTab === "mine" && publisher && (
@@ -751,14 +782,19 @@ export default function ScheduledTasksPage() {
) : (
<div className="flex flex-col gap-3 p-4 pt-0">
{publishedLoading && (
<div className="text-muted-foreground text-sm">正在加载广场任务...</div>
)}
{!publishedLoading && published.length === 0 && (
<div className="text-muted-foreground rounded-md border p-4 text-sm">
广场上还没有发布的任务。
<div className="text-muted-foreground flex items-center gap-2 text-sm">
<Loader2 className="size-4 animate-spin" />
正在加载广场任务...
</div>
)}
{published.map((item) => {
{!publishedLoading && visiblePublished.length === 0 && (
<div className="text-muted-foreground rounded-md border p-4 text-sm">
{plazaSearch.trim()
? `没有匹配「${plazaSearch.trim()}」的任务。`
: "广场上还没有发布的任务。"}
</div>
)}
{visiblePublished.map((item) => {
const subscribed = tasks.some((t) => t.task_id === item.task_id);
return (
<div key={item.task_id} className="bg-card rounded-md border p-4">