Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/web/src/components/FederatedSearchCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExternalLink, Globe } from 'lucide-react';
import { Dna, ExternalLink, Globe } from 'lucide-react';
import { Link } from 'react-router-dom';
import type { FederatedGeneItem } from '@/api/client';
import { Badge } from './ui/badge';
Expand All @@ -11,7 +11,7 @@ export default function FederatedSearchCard({ item }: { item: FederatedGeneItem
<Card className="p-5 h-full hover:shadow-md hover:border-primary/30 transition-all">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2.5 min-w-0">
<span className="text-xl shrink-0">🧬</span>
<Dna className="w-5 h-5 shrink-0 text-primary" />
<h3 className="font-semibold text-gray-900 group-hover:text-primary transition truncate">
{item.clawhub_display_name || item.name}
</h3>
Expand Down
13 changes: 11 additions & 2 deletions packages/web/src/pages/Browse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default function Browse() {
const [loading, setLoading] = useState(true);
const { isAdmin } = useAuth();

const [listError, setListError] = useState<string | null>(null);
const [federatedMode, setFederatedMode] = useState(false);
const [federatedItems, setFederatedItems] = useState<FederatedGeneItem[]>([]);
const [federatedSources, setFederatedSources] = useState<{ local: number; clawhub: number }>({
Expand Down Expand Up @@ -81,6 +82,7 @@ export default function Browse() {
useEffect(() => {
if (federatedMode && q.trim()) {
setLoading(true);
setListError(null);
federatedSearch({ q, category: category || undefined, limit: 20 })
.then((result) => {
setFederatedItems(result.items);
Expand All @@ -91,12 +93,14 @@ export default function Browse() {
.catch(() => {
setFederatedItems([]);
setTotal(0);
Comment on lines 94 to 95

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

联邦搜索失败时只清空了 federatedItems/total,但没有重置 federatedSources。如果用户上一次联邦搜索成功过,失败后 Header 仍可能显示旧的来源计数(本地/ClawHub),与当前错误状态不一致。建议在该 catch 分支里同时将 federatedSources 重置为 0 计数(必要时也可重置 totalPages)。

Suggested change
setFederatedItems([]);
setTotal(0);
setFederatedItems([]);
setFederatedSources({ local: 0, clawhub: 0 });
setTotal(0);
setTotalPages(0);

Copilot uses AI. Check for mistakes.
setListError('搜索加载失败,请刷新重试');
})
.finally(() => setLoading(false));
return;
}

setLoading(true);
setListError(null);
listGenes({
q: q || undefined,
category: category || undefined,
Expand All @@ -116,6 +120,7 @@ export default function Browse() {
.catch(() => {
setGenes([]);
setTotal(0);
setListError('列表加载失败,请刷新重试');
})
.finally(() => setLoading(false));
}, [q, category, tag, compatibility, sort, page, federatedMode, isAdmin, reviewStatus]);
Expand Down Expand Up @@ -258,10 +263,14 @@ export default function Browse() {
</div>
))}
</div>
) : listError ? (
<div className="text-center py-20">
<p className="text-red-600 text-sm">{listError}</p>
</div>
) : showFederated ? (
federatedItems.length === 0 ? (
<div className="text-center py-20">
<div className="text-4xl mb-4">🔍</div>
<Search className="w-12 h-12 mx-auto mb-4 text-muted" />
<p className="text-muted">没有找到匹配的基因</p>
</div>
) : (
Expand All @@ -273,7 +282,7 @@ export default function Browse() {
)
) : genes.length === 0 ? (
<div className="text-center py-20">
<div className="text-4xl mb-4">🔍</div>
<Search className="w-12 h-12 mx-auto mb-4 text-muted" />
<p className="text-muted">没有找到匹配的基因</p>
</div>
) : (
Expand Down
13 changes: 10 additions & 3 deletions packages/web/src/pages/GeneDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AlertCircle,
Bot,
Calendar,
Check,
Expand Down Expand Up @@ -185,22 +186,24 @@ export default function GeneDetail() {
const { slug } = useParams<{ slug: string }>();
const [gene, setGene] = useState<Gene | null>(null);
const [versions, setVersions] = useState<GeneVersion[]>([]);
const [versionsError, setVersionsError] = useState<string | null>(null);
const [error, setError] = useState('');

useEffect(() => {
if (!slug) return;
getGene(slug)
.then(setGene)
.catch(() => setError('找不到该基因'));
setVersionsError(null);
getGeneVersions(slug)
.then(setVersions)
.catch(() => {});
.catch(() => setVersionsError('版本历史加载失败,请刷新重试'));
}, [slug]);
Comment on lines 192 to 201

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error 状态在 slug 变化时没有重置:如果先进入一个不存在的基因导致 setError(...),随后在 SPA 内导航到一个有效的 slug,组件复用会让 error 持续为真而一直显示错误页。建议在 effect 开始时清空 error(并可同时将 gene 置空以回到 loading skeleton),再发起新的请求。

Copilot uses AI. Check for mistakes.

if (error) {
return (
<div className="max-w-6xl mx-auto px-4 py-20 text-center">
<div className="text-5xl mb-4">😵</div>
<AlertCircle className="w-14 h-14 mx-auto mb-4 text-muted" />
<p className="text-xl text-gray-900 mb-2">{error}</p>
<Link to="/browse" className="text-primary hover:underline">
返回浏览
Expand Down Expand Up @@ -390,7 +393,11 @@ export default function GeneDetail() {
<TabsContent value="reviews">{slug && <ReviewList slug={slug} />}</TabsContent>

<TabsContent value="versions">
<VersionHistory versions={versions} slug={gene.slug} />
{versionsError ? (
<p className="text-sm text-red-600">{versionsError}</p>
) : (
<VersionHistory versions={versions} slug={gene.slug} />
)}
</TabsContent>
</Tabs>
</div>
Expand Down
7 changes: 7 additions & 0 deletions packages/web/src/pages/GenomeBrowse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function GenomeBrowse() {
const [total, setTotal] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const [loading, setLoading] = useState(true);
const [listError, setListError] = useState<string | null>(null);
const { isAdmin } = useAuth();

const q = searchParams.get('q') || '';
Expand All @@ -42,6 +43,7 @@ export default function GenomeBrowse() {

useEffect(() => {
setLoading(true);
setListError(null);
listGenomes({
q: q || undefined,
category: category || undefined,
Expand All @@ -58,6 +60,7 @@ export default function GenomeBrowse() {
.catch(() => {
setGenomes([]);
setTotal(0);

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

列表请求失败时只重置了 genomes/total,但没有重置 totalPages。如果用户先成功加载过多页数据,再遇到一次失败,会在错误提示下方仍然显示旧的分页控件(因为 totalPages 还是旧值)。建议在 catch 分支里同时将 totalPages 置为 0(或 1),以避免展示陈旧分页。

Suggested change
setTotal(0);
setTotal(0);
setTotalPages(0);

Copilot uses AI. Check for mistakes.
setListError('列表加载失败,请刷新重试');
})
.finally(() => setLoading(false));
}, [q, category, sort, page, isAdmin]);
Expand Down Expand Up @@ -119,6 +122,10 @@ export default function GenomeBrowse() {
</div>
))}
</div>
) : listError ? (
<div className="text-center py-20">
<p className="text-red-600 text-sm">{listError}</p>
</div>
) : genomes.length === 0 ? (
<div className="text-center py-20">
<div className="text-4xl mb-4">🧫</div>
Expand Down
10 changes: 8 additions & 2 deletions packages/web/src/pages/GenomeDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,18 @@ export default function GenomeDetail() {
const { slug } = useParams<{ slug: string }>();
const [genome, setGenome] = useState<Genome | null>(null);
const [versions, setVersions] = useState<GeneVersion[]>([]);
const [versionsError, setVersionsError] = useState<string | null>(null);
const [error, setError] = useState('');

useEffect(() => {
if (!slug) return;
getGenome(slug)
.then(setGenome)
.catch(() => setError('找不到该基因组'));
setVersionsError(null);
getGenomeVersions(slug)
.then(setVersions)
.catch(() => {});
.catch(() => setVersionsError('版本历史加载失败,请刷新重试'));
}, [slug]);
Comment on lines 138 to 147

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error 状态在 slug 变化时没有重置:如果先进入一个不存在的基因组导致 setError(...),随后在 SPA 内导航到一个有效的 slug,组件复用会让 error 持续为真而一直显示错误页。建议在 effect 开始时清空 error(并可同时将 genome 置空以回到 loading skeleton),再发起新的请求。

Copilot uses AI. Check for mistakes.

if (error) {
Expand Down Expand Up @@ -263,7 +265,11 @@ export default function GenomeDetail() {
</TabsContent>

<TabsContent value="versions">
<VersionHistory versions={versions} slug={genome.slug} entityType="genome" />
{versionsError ? (
<p className="text-sm text-red-600">{versionsError}</p>
) : (
<VersionHistory versions={versions} slug={genome.slug} entityType="genome" />
)}
</TabsContent>
</Tabs>
</div>
Expand Down
24 changes: 18 additions & 6 deletions packages/web/src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArrowRight, CheckCircle, Search } from 'lucide-react';
import { ArrowRight, Bot, CheckCircle, Dna, Plug, Rocket, Search } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { type Gene, listGenes } from '@/api/client';
Expand Down Expand Up @@ -80,14 +80,26 @@ export default function Home() {
</button>
</div>
</form>
<div className="mt-6 flex justify-center gap-4 text-sm text-white/60 flex-wrap">
<span>🧬 {totalGenes > 0 ? `${totalGenes} 个基因` : '基因持续上新'}</span>
<div className="mt-6 flex justify-center items-center gap-4 text-sm text-white/60 flex-wrap">
<span className="flex items-center gap-1.5">
<Dna className="w-4 h-4 shrink-0" />
{totalGenes > 0 ? `${totalGenes} 个基因` : '基因持续上新'}
</span>
<span>•</span>
<span>🚀 L0-L3 学习协议</span>
<span className="flex items-center gap-1.5">
<Rocket className="w-4 h-4 shrink-0" />
L0-L3 学习协议
</span>
<span>•</span>
<span>🔌 多平台兼容</span>
<span className="flex items-center gap-1.5">
<Plug className="w-4 h-4 shrink-0" />
多平台兼容
</span>
<span>•</span>
<span>🤖 AI Curator 自动审核</span>
<span className="flex items-center gap-1.5">
<Bot className="w-4 h-4 shrink-0" />
AI Curator 自动审核
</span>
</div>
Comment on lines +83 to 103

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR 标题提到“移除 emoji 改用 Lucide 图标”,但当前仓库里仍保留了一些 emoji(例如本文件的 CATEGORIES 里仍用 emoji 作为分类图标,另外 GenomeBrowse 的空状态也仍有 emoji)。如果目标是全量移除,建议继续替换;如果只是替换部分场景,建议调整标题以避免误导。

Copilot uses AI. Check for mistakes.
</div>
</section>
Expand Down
7 changes: 7 additions & 0 deletions packages/web/src/pages/TemplateBrowse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function TemplateBrowse() {
const [total, setTotal] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const [loading, setLoading] = useState(true);
const [listError, setListError] = useState<string | null>(null);
const { isAdmin } = useAuth();

const q = searchParams.get('q') || '';
Expand All @@ -42,6 +43,7 @@ export default function TemplateBrowse() {

useEffect(() => {
setLoading(true);
setListError(null);
listTemplates({
q: q || undefined,
category: category || undefined,
Expand All @@ -58,6 +60,7 @@ export default function TemplateBrowse() {
.catch(() => {
setTemplates([]);
setTotal(0);

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

列表请求失败时只重置了 templates/total,但没有重置 totalPages。如果用户先成功加载过多页数据,再遇到一次失败,现在会在错误提示下方仍然显示旧的分页控件(因为 totalPages 还是旧值)。建议在 catch 分支里同时将 totalPages 置为 0(或 1),以避免展示陈旧分页。

Suggested change
setTotal(0);
setTotal(0);
setTotalPages(0);

Copilot uses AI. Check for mistakes.
setListError('列表加载失败,请刷新重试');
})
.finally(() => setLoading(false));
}, [q, category, sort, page, isAdmin]);
Expand Down Expand Up @@ -117,6 +120,10 @@ export default function TemplateBrowse() {
</div>
))}
</div>
) : listError ? (
<div className="text-center py-20">
<p className="text-red-600 text-sm">{listError}</p>
</div>
) : templates.length === 0 ? (
<div className="text-center py-20">
<p className="text-muted">暂无 AI 员工模板</p>
Expand Down
10 changes: 8 additions & 2 deletions packages/web/src/pages/TemplateDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,18 @@ export default function TemplateDetail() {
const { slug } = useParams<{ slug: string }>();
const [template, setTemplate] = useState<AgentTemplate | null>(null);
const [versions, setVersions] = useState<GeneVersion[]>([]);
const [versionsError, setVersionsError] = useState<string | null>(null);
const [error, setError] = useState('');

useEffect(() => {
if (!slug) return;
getTemplate(slug)
.then(setTemplate)
.catch(() => setError('找不到该 AI 员工模板'));
setVersionsError(null);
getTemplateVersions(slug)
.then(setVersions)
.catch(() => {});
.catch(() => setVersionsError('版本历史加载失败,请刷新重试'));
}, [slug]);
Comment on lines 148 to 157

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error 状态在 slug 变化时没有重置:如果先进入一个不存在的模板导致 setError(...),随后在 SPA 内导航到一个有效的 slug,该组件会复用同一实例,error 仍为真从而一直停留在错误页。建议在 effect 开始时清空 error(并可同时将 template 置空以回到 loading skeleton),再发起新的请求。

Copilot uses AI. Check for mistakes.

if (error) {
Expand Down Expand Up @@ -322,7 +324,11 @@ export default function TemplateDetail() {
</TabsContent>

<TabsContent value="versions">
<VersionHistory versions={versions} slug={template.slug} entityType="template" />
{versionsError ? (
<p className="text-sm text-red-600">{versionsError}</p>
) : (
<VersionHistory versions={versions} slug={template.slug} entityType="template" />
)}
</TabsContent>
</Tabs>
</div>
Expand Down