diff --git a/frontend-web/src/components/page-sdebar.css b/frontend-web/src/components/page-sdebar.css
index 769379b1..06ad158e 100644
--- a/frontend-web/src/components/page-sdebar.css
+++ b/frontend-web/src/components/page-sdebar.css
@@ -1,27 +1,42 @@
.sidebar-contents{
width:98px;
- height:986px;
+ min-height:986px;
background:rgba(3, 38, 80, 0.8);
border:1px solid;
border-color:rgba(43, 143, 254, 0.6);
border-radius:4px;
backdrop-filter:blur(2px);
display: flex;
- justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ padding: 12px 10px;
}
.item-nav{
- width:78px;
- height:70px;
- background:rgba(0, 12, 28, 0.15);
- border:1px solid;
- border-color:rgba(85, 166, 254, 0.2);
- line-height:28px;
- font-family:Noto Sans S Chinese;
+ width:100%;
+ padding:14px 0;
+ border-bottom:1px solid rgba(85, 166, 254, 0.2);
+ font-family:'Noto Sans S Chinese', sans-serif;
font-weight:500;
color:rgba(255, 255, 255, 0.8);
font-size:20px;
- padding-bottom:18px;
+ line-height:26px;
text-align:center;
- margin: 10px;
+ cursor:pointer;
+ transition:background 0.2s ease, color 0.2s ease;
+}
+
+.item-nav:hover{
+ background:rgba(85, 166, 254, 0.12);
+ color:#ffffff;
+}
+
+.item-nav__label{
+ display:block;
+ width:3em;
+ margin:0 auto;
+ padding-left:6px;
+ box-sizing:border-box;
+ letter-spacing:6px;
+ word-break:break-all;
}
\ No newline at end of file
diff --git a/frontend-web/src/components/page-sidebar-dark.tsx b/frontend-web/src/components/page-sidebar-dark.tsx
index d67f34b1..cf24537d 100644
--- a/frontend-web/src/components/page-sidebar-dark.tsx
+++ b/frontend-web/src/components/page-sidebar-dark.tsx
@@ -332,9 +332,11 @@ export function PageSidebarDark({
{/* TODO: 在这里编写深色主题的侧边栏 HTML 结构 */}
-
- 问答管理
-
+ {visibleMenuItems.map((item) => (
+
+ {item.label}
+
+ ))}
>
diff --git a/frontend-web/src/components/workspace/agents/agent-card.tsx b/frontend-web/src/components/workspace/agents/agent-card.tsx
index 7f6df666..9d4d7d1a 100644
--- a/frontend-web/src/components/workspace/agents/agent-card.tsx
+++ b/frontend-web/src/components/workspace/agents/agent-card.tsx
@@ -9,6 +9,7 @@ import {
ShieldIcon,
SparklesIcon,
SquarePenIcon,
+ StarIcon,
TagsIcon,
Trash2Icon,
} from "lucide-react";
@@ -45,6 +46,7 @@ import {
useDeleteAgent,
useSetAgentFeatured,
useSetAgentPrivate,
+ useToggleAgentFavorite,
useUpdateAgent,
} from "@/core/agents";
import type { Agent } from "@/core/agents";
@@ -65,6 +67,7 @@ export function AgentCard({ agent }: AgentCardProps) {
const updateAgent = useUpdateAgent();
const setFeatured = useSetAgentFeatured();
const setPrivate = useSetAgentPrivate();
+ const toggleFavorite = useToggleAgentFavorite();
const { skills, isLoading: isSkillsLoading } = useSkills();
const [deleteOpen, setDeleteOpen] = useState(false);
const [editOpen, setEditOpen] = useState(false);
@@ -99,6 +102,10 @@ export function AgentCard({ agent }: AgentCardProps) {
// everyone so toggling published is moot.
const canEdit = isOwned || isAdmin;
const canDelete = isOwned || isAdmin;
+ // Favoriting collects a square agent into "我的"; pointless for one you
+ // already own, so only offer it on built-in / others' agents.
+ const canFavorite = !isOwned;
+ const isFavorite = !!agent.is_favorite;
useEffect(() => {
if (!editOpen) {
@@ -217,6 +224,16 @@ export function AgentCard({ agent }: AgentCardProps) {
}
}
+ async function handleToggleFavorite() {
+ const next = !isFavorite;
+ try {
+ await toggleFavorite.mutateAsync({ id: agent.id, favorite: next });
+ toast.success(next ? "已收藏到「我的」" : "已取消收藏");
+ } catch (err) {
+ toast.error(err instanceof Error ? err.message : String(err));
+ }
+ }
+
async function handleToggleFeatured() {
// One-click on/off — ordering is handled by the dedicated 管理 Agent 列表
// dialog. Featured agents get appended to the end on add (Date.now() is a
@@ -367,8 +384,25 @@ export function AgentCard({ agent }: AgentCardProps) {
)}
- {(canEdit || canDelete || isOwned || isAdmin) && (
+ {(canEdit || canDelete || isOwned || isAdmin || canFavorite) && (
+ {canFavorite && (
+
+ )}
{isAdmin && (
)}
+
+ {isNewThread && (
+
{
+ const current = textInput.value?.trim() ?? "";
+ textInput.setInput(current ? `${current}\n${content}` : content);
+ setTimeout(() => {
+ document
+ .querySelector("textarea[name='message']")
+ ?.focus();
+ }, 0);
+ }}
+ />
+ )}
+