From 8332a0dab66902a7d27be06ec464a9050ea615ec Mon Sep 17 00:00:00 2001 From: wizardchen Date: Sat, 16 May 2026 23:04:03 +0800 Subject: [PATCH] feat(favorites): implement user favorites and recents functionality Introduced a new API client for managing user favorites, allowing users to list, add, and remove starred resources. Enhanced the sidebar to display favorites and recents, improving user navigation. Updated internationalization files to include relevant labels and tooltips for the new features across multiple languages. Refs: #1303 --- frontend/src/api/agent/index.ts | 13 +- frontend/src/api/knowledge-base/index.ts | 14 +- frontend/src/api/user-favorites.ts | 34 ++ frontend/src/components/ListSpaceSidebar.vue | 117 +++++-- .../src/components/ResourceOriginBadge.vue | 149 ++++++++ frontend/src/composables/useListUrlState.ts | 111 ++++++ frontend/src/composables/useResourcePins.ts | 260 ++++++++++++++ frontend/src/i18n/locales/en-US.ts | 46 ++- frontend/src/i18n/locales/ko-KR.ts | 44 ++- frontend/src/i18n/locales/ru-RU.ts | 48 ++- frontend/src/i18n/locales/zh-CN.ts | 200 ++++++----- frontend/src/stores/auth.ts | 25 ++ frontend/src/views/agent/AgentList.vue | 176 +++++++++- .../src/views/knowledge/KnowledgeBaseList.vue | 320 +++++++++++++----- frontend/src/views/settings/Settings.vue | 63 ++-- .../repository/user_resource_favorite.go | 80 +++++ .../service/user_resource_favorite.go | 64 ++++ internal/container/container.go | 3 + internal/handler/custom_agent.go | 28 ++ internal/handler/knowledgebase.go | 26 ++ internal/handler/user_resource_favorite.go | 144 ++++++++ internal/router/router.go | 18 + .../interfaces/user_resource_favorite.go | 33 ++ internal/types/user_resource_favorite.go | 41 +++ migrations/sqlite/000000_init.down.sql | 1 + migrations/sqlite/000000_init.up.sql | 16 + .../000047_user_resource_favorites.down.sql | 8 + .../000047_user_resource_favorites.up.sql | 43 +++ 28 files changed, 1874 insertions(+), 251 deletions(-) create mode 100644 frontend/src/api/user-favorites.ts create mode 100644 frontend/src/components/ResourceOriginBadge.vue create mode 100644 frontend/src/composables/useListUrlState.ts create mode 100644 frontend/src/composables/useResourcePins.ts create mode 100644 internal/application/repository/user_resource_favorite.go create mode 100644 internal/application/service/user_resource_favorite.go create mode 100644 internal/handler/user_resource_favorite.go create mode 100644 internal/types/interfaces/user_resource_favorite.go create mode 100644 internal/types/user_resource_favorite.go create mode 100644 migrations/versioned/000047_user_resource_favorites.down.sql create mode 100644 migrations/versioned/000047_user_resource_favorites.up.sql diff --git a/frontend/src/api/agent/index.ts b/frontend/src/api/agent/index.ts index c2cda726..9a783693 100644 --- a/frontend/src/api/agent/index.ts +++ b/frontend/src/api/agent/index.ts @@ -135,8 +135,17 @@ export const BUILTIN_AGENT_AGENT_ID = BUILTIN_SMART_REASONING_ID; // 获取智能体列表(包括内置智能体) // disabled_own_agent_ids: 当前租户在对话下拉中停用的「我的」智能体 ID,仅影响本租户 -export function listAgents() { - return get<{ data: CustomAgent[]; disabled_own_agent_ids?: string[] }>('/api/v1/agents'); +export function listAgents(params?: { + /** + * Optional creator filter; mirrors listKnowledgeBases. Built-in agents + * (is_builtin=true) are always returned regardless of this filter so + * the conversation dropdown never silently loses quick-answer / + * smart-reasoning when a user picks "Created by me". + */ + creator?: 'all' | 'mine' | 'others'; +}) { + const qs = params?.creator && params.creator !== 'all' ? `?creator=${params.creator}` : ''; + return get<{ data: CustomAgent[]; disabled_own_agent_ids?: string[] }>(`/api/v1/agents${qs}`); } // 获取智能体详情 diff --git a/frontend/src/api/knowledge-base/index.ts b/frontend/src/api/knowledge-base/index.ts index 61b6bb38..dfed0590 100644 --- a/frontend/src/api/knowledge-base/index.ts +++ b/frontend/src/api/knowledge-base/index.ts @@ -1,9 +1,21 @@ import { get, post, put, del, postUpload, getDown } from "../../utils/request"; // 知识库管理 API(列表、创建、获取、更新、删除、复制) -export function listKnowledgeBases(params?: { agent_id?: string }) { +export function listKnowledgeBases(params?: { + agent_id?: string; + /** + * Optional creator filter. Server-side semantics: + * - "mine" → only KBs whose creator_id matches the caller + * - "others" → only KBs created by someone else in this tenant + * - omitted/"all" → no filter + * KBs predating the RBAC backfill (creator_id="") never match + * mine/others — they fall out of both views by design. + */ + creator?: 'all' | 'mine' | 'others'; +}) { const query = new URLSearchParams(); if (params?.agent_id) query.set('agent_id', params.agent_id); + if (params?.creator && params.creator !== 'all') query.set('creator', params.creator); const qs = query.toString(); return get(qs ? `/api/v1/knowledge-bases?${qs}` : '/api/v1/knowledge-bases'); } diff --git a/frontend/src/api/user-favorites.ts b/frontend/src/api/user-favorites.ts new file mode 100644 index 00000000..8de318e4 --- /dev/null +++ b/frontend/src/api/user-favorites.ts @@ -0,0 +1,34 @@ +// API client for per-user starred resources (DB-backed; see migration +// 000047 and internal/handler/user_resource_favorite.go). +// +// The backend authoritatively scopes favorites to the active (user, tenant) +// pair from the auth context — these helpers therefore never pass user_id +// or tenant_id, and a tenant switch automatically points reads at the +// right namespace. + +import { get, post, del } from '@/utils/request' + +export type FavoriteResourceType = 'kb' | 'agent' + +export interface FavoriteEntry { + user_id: string + tenant_id: number + resource_type: FavoriteResourceType + resource_id: string + /** ISO timestamp from the server (created_at column). */ + created_at: string +} + +export function listFavorites(type: FavoriteResourceType) { + return get<{ success: boolean; data: FavoriteEntry[] }>( + `/api/v1/user/favorites?type=${encodeURIComponent(type)}` + ) +} + +export function addFavorite(type: FavoriteResourceType, id: string) { + return post('/api/v1/user/favorites', { type, id }) +} + +export function removeFavorite(type: FavoriteResourceType, id: string) { + return del(`/api/v1/user/favorites/${encodeURIComponent(type)}/${encodeURIComponent(id)}`) +} diff --git a/frontend/src/components/ListSpaceSidebar.vue b/frontend/src/components/ListSpaceSidebar.vue index 132c7481..bffa8b3d 100644 --- a/frontend/src/components/ListSpaceSidebar.vue +++ b/frontend/src/components/ListSpaceSidebar.vue @@ -11,19 +11,32 @@ {{ $t('listSpaceSidebar.all') }} - -
- - {{ $t('listSpaceSidebar.mine') }} -
-
- -
- - {{ $t('listSpaceSidebar.sharedToMe') }} +
+ + {{ $t('listSpaceSidebar.favorites') }}
+ +
+ + {{ $t('listSpaceSidebar.recents') }} +
+
+ +
+ + {{ workspaceLabel }} +
+
+