From e5a92427a4dab4f521b9e052bbb1904f385dde63 Mon Sep 17 00:00:00 2001 From: chiguyong Date: Sun, 21 Jun 2026 19:34:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20U9=20=E2=80=94=20frontend=20Admi?= =?UTF-8?q?nLayout=20+=207=20management=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AdminLayout with sidebar nav + 7 admin views (dashboard, departments, users, llm, skills, kb, usage). AdminApiClient extended with 40+ methods. Router restructured with nested admin routes. typecheck + build pass. --- src/agentkit/server/frontend/components.d.ts | 14 + src/agentkit/server/frontend/src/api/admin.ts | 554 ++++++++++++++- .../src/components/layout/AdminLayout.vue | 209 ++++++ .../frontend/src/components/layout/TopNav.vue | 4 +- .../settings/ChangePasswordPanel.vue | 1 + .../server/frontend/src/router/index.ts | 57 +- .../src/views/admin/DashboardView.vue | 208 ++++++ .../src/views/admin/DepartmentsView.vue | 382 +++++++++++ .../src/views/admin/KbManagementView.vue | 369 ++++++++++ .../src/views/admin/LlmConfigView.vue | 641 ++++++++++++++++++ .../frontend/src/views/admin/SkillsView.vue | 271 ++++++++ .../src/views/admin/UsageDashboardView.vue | 413 +++++++++++ .../frontend/src/views/admin/UsersView.vue | 486 ++++++++++++- .../static/assets/TerminalView-Dg1PpXnU.css | 1 - src/agentkit/server/static/index.html | 4 +- 15 files changed, 3589 insertions(+), 25 deletions(-) create mode 100644 src/agentkit/server/frontend/src/components/layout/AdminLayout.vue create mode 100644 src/agentkit/server/frontend/src/views/admin/DashboardView.vue create mode 100644 src/agentkit/server/frontend/src/views/admin/DepartmentsView.vue create mode 100644 src/agentkit/server/frontend/src/views/admin/KbManagementView.vue create mode 100644 src/agentkit/server/frontend/src/views/admin/LlmConfigView.vue create mode 100644 src/agentkit/server/frontend/src/views/admin/SkillsView.vue create mode 100644 src/agentkit/server/frontend/src/views/admin/UsageDashboardView.vue delete mode 100644 src/agentkit/server/static/assets/TerminalView-Dg1PpXnU.css diff --git a/src/agentkit/server/frontend/components.d.ts b/src/agentkit/server/frontend/components.d.ts index 3897cd6..7f9ead0 100644 --- a/src/agentkit/server/frontend/components.d.ts +++ b/src/agentkit/server/frontend/components.d.ts @@ -23,7 +23,9 @@ declare module 'vue' { ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] ADivider: typeof import('ant-design-vue/es')['Divider'] + AdminLayout: typeof import('./src/components/layout/AdminLayout.vue')['default'] ADrawer: typeof import('ant-design-vue/es')['Drawer'] + ADropdown: typeof import('ant-design-vue/es')['Dropdown'] AEmpty: typeof import('ant-design-vue/es')['Empty'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] @@ -32,18 +34,30 @@ declare module 'vue' { AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] + ALayout: typeof import('ant-design-vue/es')['Layout'] + ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] + ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader'] + ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] + AList: typeof import('ant-design-vue/es')['List'] + AListItem: typeof import('ant-design-vue/es')['ListItem'] + AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta'] + AMenu: typeof import('ant-design-vue/es')['Menu'] + AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] AModal: typeof import('ant-design-vue/es')['Modal'] + APageHeader: typeof import('ant-design-vue/es')['PageHeader'] APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] AppLayout: typeof import('./src/components/layout/AppLayout.vue')['default'] ApprovalNode: typeof import('./src/components/workflow/ApprovalNode.vue')['default'] ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] + ARangePicker: typeof import('ant-design-vue/es/date-picker/dayjs')['RangePicker'] ARow: typeof import('ant-design-vue/es')['Row'] ASelect: typeof import('ant-design-vue/es')['Select'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASpace: typeof import('ant-design-vue/es')['Space'] ASpin: typeof import('ant-design-vue/es')['Spin'] AssistantText: typeof import('./src/components/chat/messages/AssistantText.vue')['default'] + AStatistic: typeof import('ant-design-vue/es')['Statistic'] ASwitch: typeof import('ant-design-vue/es')['Switch'] ATable: typeof import('ant-design-vue/es')['Table'] ATabPane: typeof import('ant-design-vue/es')['TabPane'] diff --git a/src/agentkit/server/frontend/src/api/admin.ts b/src/agentkit/server/frontend/src/api/admin.ts index 98448c8..a68979a 100644 --- a/src/agentkit/server/frontend/src/api/admin.ts +++ b/src/agentkit/server/frontend/src/api/admin.ts @@ -1,29 +1,129 @@ /** - * Admin API client (U9 — Admin UI: user sessions management). + * Admin API client (U9 — Admin Console). * * Talks to the server-side ``/api/v1/admin/...`` endpoints implemented - * in ``src/agentkit/server/routes/auth.py``. The endpoints require + * in ``src/agentkit/server/routes/auth.py`` (sessions) and + * ``src/agentkit/server/routes/admin.py`` (departments, users, LLM + * config, quotas, skills, KB, usage). All endpoints require the * ``USER_MANAGE`` permission (admin role); the API client does not * itself enforce that — the server's 403 response is surfaced to the * caller as an :class:`IApiError` with ``status: 403``. * - * Surface: - * - ``listAllSessions`` — every recent session across the system - * (admin "active sessions" overview). - * - ``listUserSessions(userId)`` — every session for a specific user, - * including revoked ones when ``includeRevoked`` is true. - * - ``revokeUserSession(userId, sid)`` — force-revoke a single session - * of a single user. + * Surface (grouped by domain): + * - Sessions: ``listAllSessions`` / ``listUserSessions`` / ``revokeUserSession`` + * - Departments: CRUD + enable/disable + skill/KB bindings + * - Users: CRUD + reset-password + department assignment + * - LLM config: provider CRUD + api-key + fallback chains + * - Quotas: per-department quota CRUD + * - Skills: enable/disable + import + reload + config update + * - KB: documents list/upload/delete + source sync/rebuild + * - Usage: summary / timeseries / by-model / top-users / export */ import { BaseApiClient } from './base' import type { ISessionInfo } from './auth' +// --------------------------------------------------------------------------- +// TypeScript interfaces — admin domain models +// --------------------------------------------------------------------------- + +/** Department record (matches backend ``departments`` table projection). */ +export interface IDepartment { + id: string + name: string + description: string + is_active: boolean + created_at: string +} + +/** Admin user record (matches backend ``users`` table projection + departments). */ +export interface IAdminUser { + id: string + username: string + email: string + role: string + is_active: boolean + is_terminal_authorized: boolean + is_server_terminal_authorized?: boolean + created_at: string + departments?: IDepartment[] +} + +/** LLM provider record (API keys are masked by the server). */ +export interface ILlmProvider { + name: string + type: string + api_key: string + base_url: string + models: Record + max_tokens: number + timeout: number +} + +/** Quota record for a department. */ +export interface IQuota { + id: string + department_id: string + quota_type: string + limit_value: number | string[] + period: string + updated_at: string +} + +/** KB document record. */ +export interface IKbDocument { + document_id: string + filename: string + source_id: string + chunks: number + status: string + created_at: string + department_id?: string +} + +/** Usage summary aggregation. */ +export interface IUsageSummary { + total_tokens: number + total_cost: number + total_requests: number + by_model: Record + by_user: Record + by_department: Record +} + +/** Usage timeseries bucket. */ +export interface IUsageTimeseries { + timestamp: string + tokens: number + cost: number + requests: number +} + +/** Per-model usage breakdown row. */ +export interface IUsageByModel { + model: string + tokens: number + cost: number + requests: number +} + +/** Top-user usage row. */ +export interface IUsageTopUser { + user_id: string + tokens: number + cost: number + requests: number +} + class AdminApiClient extends BaseApiClient { constructor(baseUrl: string = '/api/v1') { super(baseUrl) } + // ----------------------------------------------------------------------- + // Sessions (existing — kept for backwards compat) + // ----------------------------------------------------------------------- + /** * List recent sessions across all users (admin only). * @@ -68,6 +168,442 @@ class AdminApiClient extends BaseApiClient { { method: 'DELETE' }, ) } + + // ----------------------------------------------------------------------- + // Departments + // ----------------------------------------------------------------------- + + async listDepartments(): Promise { + return this.request('/admin/departments') + } + + async createDepartment(name: string, description: string): Promise { + return this.request('/admin/departments', { + method: 'POST', + body: JSON.stringify({ name, description }), + }) + } + + async getDepartment(id: string): Promise { + return this.request(`/admin/departments/${encodeURIComponent(id)}`) + } + + async updateDepartment( + id: string, + data: { name?: string; description?: string }, + ): Promise { + return this.request(`/admin/departments/${encodeURIComponent(id)}`, { + method: 'PATCH', + body: JSON.stringify(data), + }) + } + + async deleteDepartment(id: string): Promise { + await this.request<{ deleted: boolean }>( + `/admin/departments/${encodeURIComponent(id)}`, + { method: 'DELETE' }, + ) + } + + async disableDepartment(id: string): Promise { + await this.request( + `/admin/departments/${encodeURIComponent(id)}/disable`, + { method: 'POST' }, + ) + } + + async enableDepartment(id: string): Promise { + await this.request( + `/admin/departments/${encodeURIComponent(id)}/enable`, + { method: 'POST' }, + ) + } + + async bindSkill(deptId: string, skillName: string): Promise { + await this.request( + `/admin/departments/${encodeURIComponent(deptId)}/skills/${encodeURIComponent(skillName)}`, + { method: 'POST' }, + ) + } + + async unbindSkill(deptId: string, skillName: string): Promise { + await this.request( + `/admin/departments/${encodeURIComponent(deptId)}/skills/${encodeURIComponent(skillName)}`, + { method: 'DELETE' }, + ) + } + + async bindKb(deptId: string, sourceId: string): Promise { + await this.request( + `/admin/departments/${encodeURIComponent(deptId)}/kb/${encodeURIComponent(sourceId)}`, + { method: 'POST' }, + ) + } + + async unbindKb(deptId: string, sourceId: string): Promise { + await this.request( + `/admin/departments/${encodeURIComponent(deptId)}/kb/${encodeURIComponent(sourceId)}`, + { method: 'DELETE' }, + ) + } + + async listDepartmentSkills(deptId: string): Promise { + return this.request( + `/admin/departments/${encodeURIComponent(deptId)}/skills`, + ) + } + + async listDepartmentKb(deptId: string): Promise { + return this.request( + `/admin/departments/${encodeURIComponent(deptId)}/kb`, + ) + } + + // ----------------------------------------------------------------------- + // Users + // ----------------------------------------------------------------------- + + async listUsers(departmentId?: string): Promise { + const qs = departmentId + ? `?department_id=${encodeURIComponent(departmentId)}` + : '' + return this.request(`/admin/users${qs}`) + } + + async createUser(data: { + username: string + email: string + password: string + role?: string + department_ids?: string[] + }): Promise { + return this.request('/admin/users', { + method: 'POST', + body: JSON.stringify(data), + }) + } + + async getUser(id: string): Promise { + return this.request(`/admin/users/${encodeURIComponent(id)}`) + } + + async updateUser(id: string, data: Partial): Promise { + return this.request(`/admin/users/${encodeURIComponent(id)}`, { + method: 'PATCH', + body: JSON.stringify(data), + }) + } + + async deleteUser(id: string): Promise { + await this.request<{ deleted: boolean }>( + `/admin/users/${encodeURIComponent(id)}`, + { method: 'DELETE' }, + ) + } + + async resetPassword(id: string, newPassword: string): Promise { + await this.request<{ reset: boolean }>( + `/admin/users/${encodeURIComponent(id)}/reset-password`, + { + method: 'POST', + body: JSON.stringify({ new_password: newPassword }), + }, + ) + } + + async assignDepartment(userId: string, deptId: string): Promise { + await this.request<{ assigned: boolean }>( + `/admin/users/${encodeURIComponent(userId)}/departments/${encodeURIComponent(deptId)}`, + { method: 'POST' }, + ) + } + + async removeDepartment(userId: string, deptId: string): Promise { + await this.request<{ removed: boolean }>( + `/admin/users/${encodeURIComponent(userId)}/departments/${encodeURIComponent(deptId)}`, + { method: 'DELETE' }, + ) + } + + // ----------------------------------------------------------------------- + // LLM Config + // ----------------------------------------------------------------------- + + async listLlmProviders(): Promise { + return this.request('/admin/llm/providers') + } + + async createLlmProvider( + name: string, + config: Record, + ): Promise { + return this.request('/admin/llm/providers', { + method: 'POST', + body: JSON.stringify({ name, ...config }), + }) + } + + async updateLlmProvider( + name: string, + config: Record, + ): Promise { + return this.request( + `/admin/llm/providers/${encodeURIComponent(name)}`, + { + method: 'PATCH', + body: JSON.stringify(config), + }, + ) + } + + async deleteLlmProvider(name: string): Promise { + await this.request<{ deleted: boolean }>( + `/admin/llm/providers/${encodeURIComponent(name)}`, + { method: 'DELETE' }, + ) + } + + async setLlmApiKey(name: string, apiKey: string): Promise { + await this.request( + `/admin/llm/providers/${encodeURIComponent(name)}/api-key`, + { + method: 'POST', + body: JSON.stringify({ api_key: apiKey }), + }, + ) + } + + async listLlmFallbacks(): Promise> { + return this.request>('/admin/llm/fallbacks') + } + + async setLlmFallback(model: string, chain: string[]): Promise { + await this.request( + `/admin/llm/fallbacks/${encodeURIComponent(model)}`, + { + method: 'PUT', + body: JSON.stringify({ chain }), + }, + ) + } + + async deleteLlmFallback(model: string): Promise { + await this.request<{ deleted: boolean }>( + `/admin/llm/fallbacks/${encodeURIComponent(model)}`, + { method: 'DELETE' }, + ) + } + + // ----------------------------------------------------------------------- + // Quotas + // ----------------------------------------------------------------------- + + async listDepartmentQuotas(deptId: string): Promise { + return this.request( + `/admin/departments/${encodeURIComponent(deptId)}/quotas`, + ) + } + + async setDepartmentQuota( + deptId: string, + data: { quota_type: string; limit_value: number | string[]; period: string }, + ): Promise { + return this.request( + `/admin/departments/${encodeURIComponent(deptId)}/quotas`, + { + method: 'PUT', + body: JSON.stringify(data), + }, + ) + } + + async deleteDepartmentQuota( + deptId: string, + quotaType: string, + period: string, + ): Promise { + const qs = `?quota_type=${encodeURIComponent(quotaType)}&period=${encodeURIComponent(period)}` + await this.request<{ deleted: boolean }>( + `/admin/departments/${encodeURIComponent(deptId)}/quotas${qs}`, + { method: 'DELETE' }, + ) + } + + // ----------------------------------------------------------------------- + // Skills + // ----------------------------------------------------------------------- + + async enableSkill(name: string): Promise { + await this.request( + `/admin/skills/${encodeURIComponent(name)}/enable`, + { method: 'POST' }, + ) + } + + async disableSkill(name: string): Promise { + await this.request( + `/admin/skills/${encodeURIComponent(name)}/disable`, + { method: 'POST' }, + ) + } + + async importSkill(yamlContent: string): Promise { + await this.request('/admin/skills/import', { + method: 'POST', + body: JSON.stringify({ yaml_content: yamlContent }), + }) + } + + async reloadSkill(name: string): Promise { + await this.request( + `/admin/skills/${encodeURIComponent(name)}/reload`, + { method: 'POST' }, + ) + } + + async updateSkillConfig( + name: string, + config: Record, + ): Promise { + await this.request(`/admin/skills/${encodeURIComponent(name)}`, { + method: 'PATCH', + body: JSON.stringify({ config }), + }) + } + + // ----------------------------------------------------------------------- + // KB + // ----------------------------------------------------------------------- + + async listKbDocuments( + sourceId?: string, + departmentId?: string, + ): Promise { + const params: string[] = [] + if (sourceId) params.push(`source_id=${encodeURIComponent(sourceId)}`) + if (departmentId) params.push(`department_id=${encodeURIComponent(departmentId)}`) + const qs = params.length ? `?${params.join('&')}` : '' + const res = await this.request<{ documents: IKbDocument[] }>( + `/admin/kb/documents${qs}`, + ) + return res?.documents ?? [] + } + + async uploadKbDocument(data: { + filename: string + content: string + source_id: string + department_id?: string + }): Promise { + return this.request('/admin/kb/documents', { + method: 'POST', + body: JSON.stringify(data), + }) + } + + async deleteKbDocument(id: string): Promise { + await this.request<{ deleted: boolean }>( + `/admin/kb/documents/${encodeURIComponent(id)}`, + { method: 'DELETE' }, + ) + } + + async syncKbSource(id: string): Promise { + await this.request( + `/admin/kb/sources/${encodeURIComponent(id)}/sync`, + { method: 'POST' }, + ) + } + + async rebuildKbSource(id: string): Promise { + await this.request( + `/admin/kb/sources/${encodeURIComponent(id)}/rebuild`, + { method: 'POST' }, + ) + } + + // ----------------------------------------------------------------------- + // Usage + // ----------------------------------------------------------------------- + + async getUsageSummary(params: { + department_id?: string + user_id?: string + start?: string + end?: string + }): Promise { + return this.request( + `/admin/usage/summary${_buildUsageQuery(params)}`, + ) + } + + async getUsageTimeseries(params: { + department_id?: string + user_id?: string + start?: string + end?: string + interval?: string + }): Promise { + const qs = _buildUsageQuery(params, params.interval ? { interval: params.interval } : {}) + return this.request(`/admin/usage/timeseries${qs}`) + } + + async getUsageByModel(params: { + department_id?: string + user_id?: string + start?: string + end?: string + }): Promise { + return this.request( + `/admin/usage/by-model${_buildUsageQuery(params)}`, + ) + } + + async getUsageTopUsers(params: { + department_id?: string + limit?: number + }): Promise { + const extra: Record = {} + if (params.limit !== undefined) extra.limit = String(params.limit) + return this.request( + `/admin/usage/top-users${_buildUsageQuery(params, extra)}`, + ) + } + + async exportUsage(params: { + department_id?: string + user_id?: string + start?: string + end?: string + format?: string + }): Promise { + const extra: Record = {} + if (params.format) extra.format = params.format + return this.request( + `/admin/usage/export${_buildUsageQuery(params, extra)}`, + ) + } +} + +/** + * Build a query string from the common usage filter params. + * + * Undefined / empty values are omitted. ``extra`` is merged in for + * endpoint-specific params (``interval``, ``limit``, ``format``). + */ +function _buildUsageQuery( + params: { department_id?: string; user_id?: string; start?: string; end?: string }, + extra: Record = {}, +): string { + const parts: string[] = [] + if (params.department_id) parts.push(`department_id=${encodeURIComponent(params.department_id)}`) + if (params.user_id) parts.push(`user_id=${encodeURIComponent(params.user_id)}`) + if (params.start) parts.push(`start=${encodeURIComponent(params.start)}`) + if (params.end) parts.push(`end=${encodeURIComponent(params.end)}`) + for (const [k, v] of Object.entries(extra)) { + parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + } + return parts.length ? `?${parts.join('&')}` : '' } export const adminApi = new AdminApiClient() diff --git a/src/agentkit/server/frontend/src/components/layout/AdminLayout.vue b/src/agentkit/server/frontend/src/components/layout/AdminLayout.vue new file mode 100644 index 0000000..6114e1b --- /dev/null +++ b/src/agentkit/server/frontend/src/components/layout/AdminLayout.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/components/layout/TopNav.vue b/src/agentkit/server/frontend/src/components/layout/TopNav.vue index c281b33..36de884 100644 --- a/src/agentkit/server/frontend/src/components/layout/TopNav.vue +++ b/src/agentkit/server/frontend/src/components/layout/TopNav.vue @@ -37,8 +37,8 @@ - - diff --git a/src/agentkit/server/frontend/src/components/settings/ChangePasswordPanel.vue b/src/agentkit/server/frontend/src/components/settings/ChangePasswordPanel.vue index d2ef7ab..57f7cda 100644 --- a/src/agentkit/server/frontend/src/components/settings/ChangePasswordPanel.vue +++ b/src/agentkit/server/frontend/src/components/settings/ChangePasswordPanel.vue @@ -157,3 +157,4 @@ async function handleSubmit(): Promise { .change-password-panel__submit { font-weight: 600; } + diff --git a/src/agentkit/server/frontend/src/router/index.ts b/src/agentkit/server/frontend/src/router/index.ts index 43cdb96..fa1c4fe 100644 --- a/src/agentkit/server/frontend/src/router/index.ts +++ b/src/agentkit/server/frontend/src/router/index.ts @@ -89,12 +89,59 @@ const routes: RouteRecordRaw[] = [ meta: { title: 'Computer Use' }, }, - // Admin: user sessions management (U9) + // Admin console (U9) — AdminLayout wraps all /admin/* child routes. + // ``requiresAdmin`` is set on the parent so the guard checks it for + // every child route (Vue Router merges parent meta into matched records). { - path: '/admin/users', - name: 'admin-users', - component: () => import('@/views/admin/UsersView.vue'), - meta: { title: '用户与会话管理', requiresAdmin: true }, + path: '/admin', + name: 'admin', + component: () => import('@/components/layout/AdminLayout.vue'), + meta: { title: '管理控制台', requiresAdmin: true }, + redirect: '/admin/dashboard', + children: [ + { + path: 'dashboard', + name: 'admin-dashboard', + component: () => import('@/views/admin/DashboardView.vue'), + meta: { title: '管理概览', requiresAdmin: true }, + }, + { + path: 'departments', + name: 'admin-departments', + component: () => import('@/views/admin/DepartmentsView.vue'), + meta: { title: '部门管理', requiresAdmin: true }, + }, + { + path: 'users', + name: 'admin-users', + component: () => import('@/views/admin/UsersView.vue'), + meta: { title: '用户与会话管理', requiresAdmin: true }, + }, + { + path: 'llm', + name: 'admin-llm', + component: () => import('@/views/admin/LlmConfigView.vue'), + meta: { title: 'LLM 配置', requiresAdmin: true }, + }, + { + path: 'skills', + name: 'admin-skills', + component: () => import('@/views/admin/SkillsView.vue'), + meta: { title: 'Skill 管理', requiresAdmin: true }, + }, + { + path: 'kb', + name: 'admin-kb', + component: () => import('@/views/admin/KbManagementView.vue'), + meta: { title: '知识库管理', requiresAdmin: true }, + }, + { + path: 'usage', + name: 'admin-usage', + component: () => import('@/views/admin/UsageDashboardView.vue'), + meta: { title: '用量仪表盘', requiresAdmin: true }, + }, + ], }, // Legacy layout (fallback) diff --git a/src/agentkit/server/frontend/src/views/admin/DashboardView.vue b/src/agentkit/server/frontend/src/views/admin/DashboardView.vue new file mode 100644 index 0000000..4fb430e --- /dev/null +++ b/src/agentkit/server/frontend/src/views/admin/DashboardView.vue @@ -0,0 +1,208 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/views/admin/DepartmentsView.vue b/src/agentkit/server/frontend/src/views/admin/DepartmentsView.vue new file mode 100644 index 0000000..ef2f5fa --- /dev/null +++ b/src/agentkit/server/frontend/src/views/admin/DepartmentsView.vue @@ -0,0 +1,382 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/views/admin/KbManagementView.vue b/src/agentkit/server/frontend/src/views/admin/KbManagementView.vue new file mode 100644 index 0000000..24dfc5a --- /dev/null +++ b/src/agentkit/server/frontend/src/views/admin/KbManagementView.vue @@ -0,0 +1,369 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/views/admin/LlmConfigView.vue b/src/agentkit/server/frontend/src/views/admin/LlmConfigView.vue new file mode 100644 index 0000000..fe42370 --- /dev/null +++ b/src/agentkit/server/frontend/src/views/admin/LlmConfigView.vue @@ -0,0 +1,641 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/views/admin/SkillsView.vue b/src/agentkit/server/frontend/src/views/admin/SkillsView.vue new file mode 100644 index 0000000..0676aee --- /dev/null +++ b/src/agentkit/server/frontend/src/views/admin/SkillsView.vue @@ -0,0 +1,271 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/views/admin/UsageDashboardView.vue b/src/agentkit/server/frontend/src/views/admin/UsageDashboardView.vue new file mode 100644 index 0000000..e0f725c --- /dev/null +++ b/src/agentkit/server/frontend/src/views/admin/UsageDashboardView.vue @@ -0,0 +1,413 @@ + + + + + diff --git a/src/agentkit/server/frontend/src/views/admin/UsersView.vue b/src/agentkit/server/frontend/src/views/admin/UsersView.vue index 55a12c3..ca653e3 100644 --- a/src/agentkit/server/frontend/src/views/admin/UsersView.vue +++ b/src/agentkit/server/frontend/src/views/admin/UsersView.vue @@ -2,7 +2,7 @@
@@ -85,31 +85,246 @@ + + + + + + + + 新建用户 + + + 刷新 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + 添加 + + +
+
+
@@ -216,4 +685,9 @@ onMounted(() => { color: var(--text-secondary, #666); font-size: 14px; } + +.users-view__dept-assign { + display: flex; + flex-direction: column; +} diff --git a/src/agentkit/server/static/assets/TerminalView-Dg1PpXnU.css b/src/agentkit/server/static/assets/TerminalView-Dg1PpXnU.css deleted file mode 100644 index 2b7fb9d..0000000 --- a/src/agentkit/server/static/assets/TerminalView-Dg1PpXnU.css +++ /dev/null @@ -1 +0,0 @@ -.terminal-emulator[data-v-e0e9656d]{display:flex;flex-direction:column;height:100%;background:var(--code-bg);border-radius:var(--radius-md);overflow:hidden;font-family:SF Mono,Fira Code,Cascadia Code,Menlo,Consolas,monospace}.terminal-emulator__output[data-v-e0e9656d]{flex:1;overflow-y:auto;padding:var(--space-3);font-size:var(--font-sm);line-height:var(--leading-normal);color:var(--code-fg)}.terminal-emulator__welcome[data-v-e0e9656d]{color:var(--code-comment);font-style:italic}.terminal-emulator__input[data-v-e0e9656d]{display:flex;align-items:center;padding:var(--space-2) var(--space-3);border-top:1px solid rgba(255,255,255,.1);background:#0003}.terminal-emulator__prompt[data-v-e0e9656d]{color:var(--code-string);margin-right:var(--space-2);font-size:var(--font-sm);white-space:nowrap}.terminal-emulator__input-field[data-v-e0e9656d]{flex:1;background:transparent;border:none;outline:none;color:var(--code-fg);font-family:inherit;font-size:var(--font-sm)}.terminal-emulator__input-field[data-v-e0e9656d]::placeholder{color:var(--code-comment)}.terminal-line[data-v-e0e9656d]{white-space:pre-wrap;word-break:break-all}.terminal-line[data-v-e0e9656d] .ansi-green{color:var(--code-string)}.terminal-line[data-v-e0e9656d] .ansi-yellow{color:var(--code-number)}.terminal-line[data-v-e0e9656d] .ansi-red{color:var(--code-variable)}.terminal-line[data-v-e0e9656d] .ansi-cyan,.terminal-line[data-v-e0e9656d] .ansi-blue{color:var(--code-function)}.terminal-line[data-v-e0e9656d] .ansi-magenta{color:var(--code-keyword)}.command-history[data-v-d8bc7055]{display:flex;flex-direction:column;height:100%;background:var(--bg-primary)}.command-history__header[data-v-d8bc7055]{display:flex;justify-content:space-between;align-items:center;padding:var(--space-3) var(--space-4);font-weight:var(--font-weight-semibold);font-size:var(--font-base);border-bottom:1px solid var(--border-color)}.command-history__list[data-v-d8bc7055]{flex:1;overflow-y:auto;padding:var(--space-2)}.command-history__item[data-v-d8bc7055]{padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);cursor:pointer;margin-bottom:var(--space-1);transition:background var(--transition-fast)}.command-history__item[data-v-d8bc7055]:hover{background:var(--color-primary-light)}.command-history__item-header[data-v-d8bc7055]{display:flex;align-items:center;gap:var(--space-1)}.command-history__exit-code[data-v-d8bc7055]{font-size:var(--font-xs);font-weight:var(--font-weight-semibold)}.command-history__exit-code--success[data-v-d8bc7055]{color:var(--color-success)}.command-history__exit-code--error[data-v-d8bc7055]{color:var(--color-error)}.command-history__command[data-v-d8bc7055]{font-family:SF Mono,Fira Code,Cascadia Code,Menlo,Consolas,monospace;font-size:var(--font-xs);color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.command-history__item-meta[data-v-d8bc7055]{display:flex;gap:var(--space-2);margin-top:2px;font-size:11px;color:var(--text-placeholder)}.command-history__duration[data-v-d8bc7055]{color:var(--color-primary)}.command-history__empty[data-v-d8bc7055]{text-align:center;padding:var(--space-6);color:var(--text-placeholder);font-size:var(--font-sm)}.terminal-view[data-v-e0e2611b]{display:flex;height:100%;overflow:hidden}.terminal-view__main[data-v-e0e2611b]{flex:1;overflow:hidden;padding:var(--space-2);position:relative}.terminal-view__sidebar[data-v-e0e2611b]{display:flex;border-left:1px solid var(--border-color);background:var(--bg-primary);transition:width var(--transition-normal);overflow:hidden}.terminal-view__sidebar--collapsed[data-v-e0e2611b]{width:32px}.terminal-view__sidebar[data-v-e0e2611b]:not(.terminal-view__sidebar--collapsed){width:240px}.terminal-view__sidebar-toggle[data-v-e0e2611b]{display:flex;align-items:center;justify-content:center;width:32px;height:100%;border:none;background:transparent;color:var(--text-tertiary);cursor:pointer;flex-shrink:0;transition:all var(--transition-fast)}.terminal-view__sidebar-toggle[data-v-e0e2611b]:hover{color:var(--text-primary);background:var(--bg-tertiary)}.terminal-view__sidebar-content[data-v-e0e2611b]{flex:1;overflow:hidden;min-width:0}.terminal-view__modal-command[data-v-e0e2611b]{font-family:SF Mono,Fira Code,Cascadia Code,Menlo,Consolas,monospace;font-size:var(--font-sm);color:var(--text-primary);background:var(--bg-tertiary);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);margin-bottom:var(--space-3);word-break:break-all}.terminal-view__modal-reason[data-v-e0e2611b]{font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:var(--space-3)} diff --git a/src/agentkit/server/static/index.html b/src/agentkit/server/static/index.html index 32cdbe8..508cee5 100644 --- a/src/agentkit/server/static/index.html +++ b/src/agentkit/server/static/index.html @@ -5,8 +5,8 @@ Fischer AgentKit - - + +