-
+
+
-
+
@@ -140,16 +140,14 @@
{m.feishuOpenId} |
-
-
+ onchange={(role) => {
+ if (role !== m.role) changeRole(m, role);
+ }}
+ />
|
{fmtDate(m.createdAt)} |
@@ -162,7 +160,7 @@
{/if}
- 组织角色控制后台访问;项目级权限由「项目」页团队授权(READ / EDIT / MANAGE)决定。
+ 组织角色控制后台访问;项目级权限由「项目」页团队授权({permHint})决定。
{/if}
diff --git a/hub/admin-web/src/routes/admin/org/[slug]/models/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/models/+page.svelte
index f14fb1d..cc34c32 100644
--- a/hub/admin-web/src/routes/admin/org/[slug]/models/+page.svelte
+++ b/hub/admin-web/src/routes/admin/org/[slug]/models/+page.svelte
@@ -6,6 +6,7 @@
import LoadingState from '$lib/components/LoadingState.svelte';
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
import EmptyState from '$lib/components/EmptyState.svelte';
+ import CheckboxControl from '$lib/components/CheckboxControl.svelte';
import { toastError, toastSuccess } from '$lib/toast';
const slug = $derived(page.params.slug ?? '');
@@ -82,7 +83,7 @@
{#if loading}
@@ -95,10 +96,10 @@
-
+
@@ -116,8 +117,8 @@
- | label |
- modelId |
+ 显示名 |
+ 模型 ID |
工具能力 |
排序 |
创建 |
@@ -142,11 +143,9 @@
/>
- save(m, { toolCapable: (e.target as HTMLInputElement).checked })}
+ onchange={(toolCapable) => save(m, { toolCapable })}
/>
|
diff --git a/hub/admin-web/src/routes/admin/org/[slug]/projects/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/projects/+page.svelte
index 0e22592..5ae82c7 100644
--- a/hub/admin-web/src/routes/admin/org/[slug]/projects/+page.svelte
+++ b/hub/admin-web/src/routes/admin/org/[slug]/projects/+page.svelte
@@ -6,7 +6,9 @@
import LoadingState from '$lib/components/LoadingState.svelte';
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
import EmptyState from '$lib/components/EmptyState.svelte';
+ import { Label } from 'bits-ui';
import Modal from '$lib/components/Modal.svelte';
+ import SelectField from '$lib/components/SelectField.svelte';
import { toastError, toastSuccess } from '$lib/toast';
const slug = $derived(page.params.slug ?? '');
@@ -81,6 +83,14 @@
return parts.join(' / ');
}
+ function folderItems() {
+ if (!data) return [{ value: '', label: '(根)' }];
+ return [
+ { value: '', label: '(根)' },
+ ...data.folders.map((f) => ({ value: f.id, label: folderPath(f) }))
+ ];
+ }
+
$effect(() => {
if (slug) load();
});
@@ -108,7 +118,7 @@
{/if}
-
+ 名称
{#if data && data.folders.length > 0}
-
-
+ 父文件夹(可选)
+
+
+
{/if}
@@ -133,7 +140,7 @@
-
+ 项目名
{#if data && data.folders.length > 0}
-
-
+ 文件夹(可选)
+
+
+
{/if}
diff --git a/hub/admin-web/src/routes/admin/org/[slug]/projects/[projectId]/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/projects/[projectId]/+page.svelte
index 2f3e2a4..1995fbc 100644
--- a/hub/admin-web/src/routes/admin/org/[slug]/projects/[projectId]/+page.svelte
+++ b/hub/admin-web/src/routes/admin/org/[slug]/projects/[projectId]/+page.svelte
@@ -8,16 +8,19 @@
type SessionSummary,
type ExplorerData
} from '$lib/api';
- import { fmtDate } from '$lib/format';
- import { PERMISSION_ROLES } from '$lib/constants';
+ import { fmtDate, permissionRoleLabel } from '$lib/format';
+ import { PERMISSION_ROLES, PERMISSION_ROLE_LABELS } from '$lib/constants';
import PageHeader from '$lib/components/PageHeader.svelte';
import LoadingState from '$lib/components/LoadingState.svelte';
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
import EmptyState from '$lib/components/EmptyState.svelte';
+ import SelectField from '$lib/components/SelectField.svelte';
import { toastError, toastSuccess } from '$lib/toast';
const slug = $derived(page.params.slug ?? '');
const projectId = $derived(page.params.projectId ?? '');
+ const roleItems = PERMISSION_ROLES.map((r) => ({ value: r, label: PERMISSION_ROLE_LABELS[r] }));
+ const roleChain = `${PERMISSION_ROLE_LABELS.READ} ⊂ ${PERMISSION_ROLE_LABELS.EDIT} ⊂ ${PERMISSION_ROLE_LABELS.MANAGE}`;
let proj = $state (null);
let access = $state([]);
@@ -69,7 +72,7 @@
}
async function archiveBinding() {
- if (!confirm('解绑当前飞书群? 用户将无法通过该群触发 agent。')) return;
+ if (!confirm('解绑当前飞书群? 用户将无法通过该群触发智能体。')) return;
try {
await api.archiveBinding(slug, projectId);
await load();
@@ -122,6 +125,22 @@
}
}
+ function folderItems() {
+ const items = [{ value: '', label: '(根)' }];
+ if (!explorer) return items;
+ return [
+ ...items,
+ ...explorer.folders.map((f) => ({ value: f.id, label: f.name }))
+ ];
+ }
+
+ function teamItems() {
+ return [
+ { value: '', label: '选择团队…' },
+ ...teams.map((t) => ({ value: t.id, label: `${t.name}(${t.slug})` }))
+ ];
+ }
+
$effect(() => {
if (slug && projectId) load();
});
@@ -139,7 +158,7 @@
{@const detail = proj}
-
+
{#snippet actions()}
{#if detail.binding}
@@ -152,7 +171,7 @@
- - Workspace
+ - 工作区路径
- {proj.workspaceDir}
@@ -170,7 +189,7 @@
-
{#if proj.binding}
已绑定
- chat {proj.binding.chatId}
+ 群 {proj.binding.chatId}
· {fmtDate(proj.binding.createdAt)}
{:else}
未绑定
@@ -184,14 +203,11 @@
{#if explorer}
-
- 移动到文件夹
-
+
{/if}
@@ -201,16 +217,9 @@
团队授权
通过团队授权项目访问。一项目可授多团队,一团队可访问多项目。
-
-
-
+
+
+
@@ -221,7 +230,7 @@
| 团队 |
- slug |
+ 标识 |
角色 |
|
@@ -231,7 +240,7 @@
| {g.teamName} |
/{g.teamSlug} |
- {g.role} |
+ {permissionRoleLabel(g.role)} |
|
@@ -244,17 +253,17 @@
- Agent 会话
+ 智能体会话
{#if sessions.length === 0}
-
+
{:else}
- | provider / role |
- model |
- 运行 |
+ 供应方 / 角色 |
+ 模型 |
+ 运行次数 |
更新 |
diff --git a/hub/admin-web/src/routes/admin/org/[slug]/provider/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/provider/+page.svelte
index 15830ec..32e4447 100644
--- a/hub/admin-web/src/routes/admin/org/[slug]/provider/+page.svelte
+++ b/hub/admin-web/src/routes/admin/org/[slug]/provider/+page.svelte
@@ -2,12 +2,16 @@
import { page } from '$app/state';
import { api, type ProviderConnection } from '$lib/api';
import { fmtDate } from '$lib/format';
+ import { PROVIDER_MODES } from '$lib/constants';
+ import { Label } from 'bits-ui';
import PageHeader from '$lib/components/PageHeader.svelte';
import LoadingState from '$lib/components/LoadingState.svelte';
import ErrorBanner from '$lib/components/ErrorBanner.svelte';
+ import SelectField from '$lib/components/SelectField.svelte';
import { toastError, toastSuccess } from '$lib/toast';
const slug = $derived(page.params.slug ?? '');
+ const modeItems = PROVIDER_MODES.map((m) => ({ value: m.value, label: m.label }));
let conn = $state(null);
let loading = $state(true);
@@ -50,7 +54,7 @@
providerId = conn.providerId;
baseUrl = conn.baseUrl ?? '';
authToken = '';
- toastSuccess('Provider 配置已保存');
+ toastSuccess('供应方配置已保存');
} catch (err) {
toastError(err instanceof Error ? err.message : String(err));
} finally {
@@ -64,8 +68,8 @@
{#if loading}
@@ -76,21 +80,24 @@
-
-
+ 凭据模式
+ {
+ if (v === 'BYOK' || v === 'PLATFORM_MANAGED') mode = v;
+ }}
+ />
-
+ 供应方 ID
{#if mode === 'BYOK'}
-
+ 接口地址
-
+ 访问令牌
{#if conn.hasAuthToken}
@@ -128,17 +135,19 @@
运行时解析
- - •BYOK:agent 使用本连接 baseUrl + authToken。
+ -
+ •
+ 自带密钥:智能体会使用本连接的接口地址与访问令牌。
+
-
•
- PLATFORM_MANAGED / 未配置:回退进程环境变量(ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN)。真正 per-org
- 平台托管凭据是后续 secret-control-plane(OPEN)。
+ 平台托管或未配置时,回退到进程环境变量中的接口地址与访问令牌。真正的按组织托管密钥方案仍在规划中。
-
•
- 模型与角色在对应页面管理;空时回退环境默认。
+ 模型与角色在对应页面管理;未配置时回退环境默认值。
diff --git a/hub/admin-web/src/routes/admin/org/[slug]/roles/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/roles/+page.svelte
index 2c07a4e..c6aac88 100644
--- a/hub/admin-web/src/routes/admin/org/[slug]/roles/+page.svelte
+++ b/hub/admin-web/src/routes/admin/org/[slug]/roles/+page.svelte
@@ -67,7 +67,7 @@
{#if loading}
@@ -78,15 +78,15 @@
{#if roles.length === 0}
-
+
{:else}
diff --git a/hub/admin-web/src/routes/admin/org/[slug]/teams/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/teams/+page.svelte
index 13dedd6..35dc3b1 100644
--- a/hub/admin-web/src/routes/admin/org/[slug]/teams/+page.svelte
+++ b/hub/admin-web/src/routes/admin/org/[slug]/teams/+page.svelte
@@ -1,4 +1,5 @@
-
+
{#if loading}
@@ -129,7 +134,7 @@
新建团队
-
+
@@ -143,16 +148,22 @@
{:else}
{#each teams as t (t.id)}
-
+ onExpandChange(t, open)}
+ >
{t.name}
/{t.slug}
{t.memberCount} 成员
-
{#if t.description}
@@ -160,15 +171,15 @@
{/if}
创建于 {fmtDate(t.createdAt)}
- {#if expandedId === t.id}
+
- {#if loadingMembers}
+ {#if loadingMembers && expandedId === t.id}
加载中…
- {:else}
+ {:else if expandedId === t.id}
{
if (e.key === 'Enter') addMember(t);
@@ -195,9 +206,9 @@
{m.feishuOpenId} |
{fmtDate(m.createdAt)} |
- revokeMember(t, m)}
- >移除
+ revokeMember(t, m)}>
+ 移除
+
|
{/each}
@@ -206,8 +217,8 @@
{/if}
{/if}
- {/if}
-
+
+
{/each}
{/if}
diff --git a/hub/admin-web/src/routes/app.css b/hub/admin-web/src/routes/app.css
index 2a31663..90c1d67 100644
--- a/hub/admin-web/src/routes/app.css
+++ b/hub/admin-web/src/routes/app.css
@@ -324,6 +324,79 @@
resize: vertical;
}
+ .saas-select-trigger {
+ display: inline-flex;
+ width: 100%;
+ align-items: center;
+ border-radius: 0.5rem;
+ border: 1px solid var(--color-surface-200);
+ background: var(--color-surface-50);
+ color: var(--color-surface-900);
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+ cursor: pointer;
+ text-align: left;
+ }
+
+ .saas-select-trigger:focus-visible,
+ .saas-select-trigger[data-state='open'] {
+ border-color: var(--color-primary-400);
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-primary-500) 20%, transparent);
+ }
+
+ .saas-select-trigger:disabled,
+ .saas-select-trigger[data-disabled] {
+ cursor: not-allowed;
+ opacity: 0.55;
+ }
+
+ .saas-select-trigger [data-placeholder] {
+ color: var(--color-surface-400);
+ }
+
+ .saas-select-content {
+ z-index: 70;
+ max-height: min(18rem, var(--bits-select-content-available-height, 18rem));
+ width: var(--bits-select-anchor-width);
+ min-width: var(--bits-select-anchor-width);
+ overflow: hidden;
+ border-radius: 0.5rem;
+ border: 1px solid var(--color-surface-200);
+ background: var(--color-surface-50);
+ box-shadow: 0 10px 30px rgb(15 23 42 / 0.12);
+ outline: none;
+ }
+
+ .saas-select-item {
+ display: flex;
+ align-items: center;
+ border-radius: 0.375rem;
+ padding: 0.4rem 0.6rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--color-surface-800);
+ cursor: pointer;
+ outline: none;
+ user-select: none;
+ }
+
+ .saas-select-item[data-highlighted] {
+ background: color-mix(in oklab, var(--color-primary-500) 12%, transparent);
+ }
+
+ .saas-select-item[data-selected] {
+ color: var(--color-primary-800);
+ font-weight: 500;
+ }
+
+ .saas-select-item[data-disabled] {
+ cursor: not-allowed;
+ opacity: 0.45;
+ }
+
.saas-btn-primary,
.saas-btn-secondary,
.saas-btn-ghost,
@@ -386,26 +459,102 @@
background: var(--color-error-200);
}
+ .saas-checkbox {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.1rem;
+ height: 1.1rem;
+ flex-shrink: 0;
+ border-radius: 0.3rem;
+ border: 1px solid var(--color-surface-300);
+ background: var(--color-surface-50);
+ color: white;
+ cursor: pointer;
+ transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
+ }
+
+ .saas-checkbox[data-state='checked'] {
+ background: var(--color-primary-600);
+ border-color: var(--color-primary-600);
+ }
+
+ .saas-checkbox:focus-visible {
+ outline: none;
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-primary-500) 20%, transparent);
+ }
+
+ .saas-checkbox[data-disabled] {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+
+ .saas-switch {
+ position: relative;
+ display: inline-flex;
+ width: 2.5rem;
+ height: 1.4rem;
+ flex-shrink: 0;
+ align-items: center;
+ border-radius: 9999px;
+ background: var(--color-surface-300);
+ padding: 0.125rem;
+ cursor: pointer;
+ transition: background 0.15s;
+ }
+
+ .saas-switch[data-state='checked'] {
+ background: var(--color-primary-600);
+ }
+
+ .saas-switch:focus-visible {
+ outline: none;
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-primary-500) 20%, transparent);
+ }
+
+ .saas-switch[data-disabled] {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+
+ .saas-switch-thumb {
+ display: block;
+ width: 1.05rem;
+ height: 1.05rem;
+ border-radius: 9999px;
+ background: white;
+ box-shadow: 0 1px 2px rgb(15 23 42 / 0.18);
+ transition: transform 0.15s;
+ transform: translateX(0);
+ }
+
+ .saas-switch[data-state='checked'] .saas-switch-thumb,
+ .saas-switch-thumb[data-state='checked'] {
+ transform: translateX(1.05rem);
+ }
+
.saas-modal-backdrop {
position: fixed;
inset: 0;
z-index: 50;
- display: flex;
- align-items: center;
- justify-content: center;
background: rgb(2 6 23 / 0.4);
- padding: 1rem;
backdrop-filter: blur(2px);
}
.saas-modal {
- width: 100%;
+ position: fixed;
+ left: 50%;
+ top: 50%;
+ z-index: 51;
+ width: calc(100% - 2rem);
max-width: 28rem;
+ transform: translate(-50%, -50%);
border-radius: 1rem;
border: 1px solid var(--color-surface-200);
background: var(--color-surface-50);
padding: 1.5rem;
box-shadow: 0 20px 40px rgb(15 23 42 / 0.16);
+ outline: none;
}
.saas-status-panel {
| |