feat: 技能收藏与常驻(always-on)支持
- 新增 skill_favorites / skill_always_on 迁移与持久化模型字段 - skills 路由、存储、前端设置页支持收藏与常驻 - 调整摘要中间件与 lead agent 提示词 - 同步岗位 position_sync 及相关配置清理 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5ebbb82d66
commit
5182ea5d11
19 files changed
+472
-107
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
AlertCircle,
|
||||
Anchor,
|
||||
ArchiveIcon,
|
||||
ArrowDownUpIcon,
|
||||
BookmarkIcon,
|
||||
@@ -77,6 +78,7 @@ import {
|
||||
useGenerateSkillNameZh,
|
||||
useInstallSkillUpload,
|
||||
useReconcileCustomSkills,
|
||||
useSetSkillAlwaysOn,
|
||||
useSetSkillFeatured,
|
||||
useSetSkillNameZh,
|
||||
useSetSkillPinned,
|
||||
@@ -84,7 +86,11 @@ import {
|
||||
useSkills,
|
||||
useValidateSkillUpload,
|
||||
} from "@/core/skills/hooks";
|
||||
import { usePublishSquares, useViewMode } from "@/core/system-settings/hooks";
|
||||
import {
|
||||
usePublishSquares,
|
||||
useSkillCompressionSettings,
|
||||
useViewMode,
|
||||
} from "@/core/system-settings/hooks";
|
||||
import type { Skill } from "@/core/skills/type";
|
||||
import { formatDateTime } from "@/core/utils/datetime";
|
||||
import { env } from "@/env";
|
||||
@@ -181,8 +187,10 @@ function SkillSettingsList({ onClose }: { onClose?: () => void }) {
|
||||
const isAdmin = storedAuth?.system_role === "admin";
|
||||
|
||||
// Name + tag filtering is resolved server-side via the /api/skills query.
|
||||
// Non-admins get their 岗位 (position) visibility applied (scope=mine): a
|
||||
// position that binds skill tags restricts which skills they can see.
|
||||
// A 岗位 (position) no longer *restricts* the list — it grants skills into the
|
||||
// member's 我的技能 (each such skill comes back with ``favorited: true``), so
|
||||
// the 广场 always shows every visible skill and the 我的 tab includes the
|
||||
// position-granted ones. ``scope`` is kept for API compatibility only.
|
||||
const { skills, isLoading, error } = useSkills({
|
||||
search,
|
||||
tagIds,
|
||||
@@ -217,7 +225,9 @@ function SkillSettingsList({ onClose }: { onClose?: () => void }) {
|
||||
if (isBuiltin || !!skill.published) {
|
||||
all.push(skill);
|
||||
}
|
||||
if (isOwnedByMe) {
|
||||
// "我的" = skills I own OR skills granted into my collection by my 岗位
|
||||
// (position) — mirroring how favorited agents land in 我的智能体.
|
||||
if (isOwnedByMe || !!skill.favorited) {
|
||||
mine.push(skill);
|
||||
}
|
||||
}
|
||||
@@ -601,6 +611,8 @@ export function SkillRow({
|
||||
const { data: cardPublishSquares } = usePublishSquares();
|
||||
const setPinned = useSetSkillPinned();
|
||||
const setFeatured = useSetSkillFeatured();
|
||||
const setAlwaysOn = useSetSkillAlwaysOn();
|
||||
const { data: compression } = useSkillCompressionSettings();
|
||||
const deleteSkill = useDeleteCustomSkill();
|
||||
const duplicateSkillMutation = useDuplicateSkill();
|
||||
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
||||
@@ -620,6 +632,17 @@ export function SkillRow({
|
||||
// Admin can edit/delete/unpublish anyone's skill; non-admin only their own.
|
||||
const canManage = isCustom && (isOwned || isAdmin);
|
||||
const canTogglePublish = canManage && !isBuiltin;
|
||||
// 常驻(免压缩):内置技能仅管理员可设;自定义技能其所有者或管理员可设(与后端一致)。
|
||||
const canSetAlwaysOn = isAdmin || isOwned;
|
||||
|
||||
async function handleToggleAlwaysOn(next: boolean) {
|
||||
try {
|
||||
await setAlwaysOn.mutateAsync({ skillName: skill.name, alwaysOn: next });
|
||||
toast.success(next ? "已设为常驻(免压缩)" : "已取消常驻");
|
||||
} catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTogglePublished(next: boolean) {
|
||||
// Publishing: prompt for which square to publish into (when configured).
|
||||
@@ -932,6 +955,27 @@ export function SkillRow({
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{compression?.enabled && canSetAlwaysOn && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"h-8 w-8 shrink-0",
|
||||
skill.always_on && "text-amber-600 hover:text-amber-700",
|
||||
)}
|
||||
disabled={setAlwaysOn.isPending}
|
||||
onClick={() => void handleToggleAlwaysOn(!skill.always_on)}
|
||||
title={
|
||||
skill.always_on
|
||||
? "取消常驻(将被压缩,改由 search_skills 检索)"
|
||||
: "设为常驻(免压缩,始终全文注入提示词)"
|
||||
}
|
||||
>
|
||||
<Anchor
|
||||
className={cn("size-3.5", skill.always_on && "fill-current")}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{canTogglePublish && (
|
||||
<Button
|
||||
size="icon"
|
||||
|
||||
Reference in new issue
Block a user