Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e874c7a8e | |||
| 75b7c07a4d | |||
| d864bcd6fb | |||
| 7c2af3306a | |||
| 23a5f147fd |
@@ -38,5 +38,9 @@ H5_USERS_ROOT=/Users/john/Project/memind_adm/data/users
|
|||||||
# 超管页「返回对话」跳转主 H5(可选)
|
# 超管页「返回对话」跳转主 H5(可选)
|
||||||
VITE_MAIN_APP_URL=https://h5.tkmind.cn
|
VITE_MAIN_APP_URL=https://h5.tkmind.cn
|
||||||
|
|
||||||
|
# Umami 仅允许从 MemindAdm 单点登录进入(必须与 memind-analytics 一致)
|
||||||
|
MEMIND_UMAMI_SSO_SECRET=replace-with-the-same-random-secret-used-by-umami
|
||||||
|
UMAMI_SSO_USERNAME=admin
|
||||||
|
|
||||||
# Plaza 帖子预览链接
|
# Plaza 帖子预览链接
|
||||||
# VITE_PLAZA_BASE=https://plaza.tkmind.cn
|
# VITE_PLAZA_BASE=https://plaza.tkmind.cn
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ fi
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MEMIND_GIT_HEAD="$(git -C "${MEMIND_SRC}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||||
|
MEMIND_GIT_BRANCH="$(git -C "${MEMIND_SRC}" branch --show-current 2>/dev/null || echo detached)"
|
||||||
|
|
||||||
say "打包 Memind 共享模块到 memind-lib"
|
say "打包 Memind 共享模块到 memind-lib"
|
||||||
rm -rf "${MEMIND_LIB}"
|
rm -rf "${MEMIND_LIB}"
|
||||||
mkdir -p "${MEMIND_LIB}"
|
mkdir -p "${MEMIND_LIB}"
|
||||||
@@ -133,10 +136,32 @@ rsync -a \
|
|||||||
--include '*.js' \
|
--include '*.js' \
|
||||||
--exclude '*' \
|
--exclude '*' \
|
||||||
"${MEMIND_SRC}/" "${MEMIND_LIB}/"
|
"${MEMIND_SRC}/" "${MEMIND_LIB}/"
|
||||||
|
|
||||||
|
if [[ -d "${MEMIND_SRC}/skills" ]]; then
|
||||||
|
say "打包 Memind 平台技能目录到 memind-lib"
|
||||||
|
mkdir -p "${MEMIND_LIB}/skills"
|
||||||
|
rsync -a \
|
||||||
|
--exclude '.DS_Store' \
|
||||||
|
--exclude '__pycache__/' \
|
||||||
|
"${MEMIND_SRC}/skills/" "${MEMIND_LIB}/skills/"
|
||||||
|
fi
|
||||||
|
|
||||||
[[ -f "${MEMIND_LIB}/user-auth.mjs" ]] || {
|
[[ -f "${MEMIND_LIB}/user-auth.mjs" ]] || {
|
||||||
echo "memind-lib 缺少 user-auth.mjs" >&2
|
echo "memind-lib 缺少 user-auth.mjs" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
[[ -f "${MEMIND_LIB}/skills/excel-analyst/SKILL.md" ]] || {
|
||||||
|
echo "memind-lib 缺少 Excel Analyst 技能" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -q 'EXCEL_ANALYST_SKILL_NAME' "${MEMIND_LIB}/skills-registry.mjs" || {
|
||||||
|
echo "memind-lib 缺少 Excel Analyst 技能授权映射" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -q 'tkmind-excel' "${MEMIND_LIB}/capabilities.mjs" || {
|
||||||
|
echo "memind-lib 缺少 Excel Analyst runtime 扩展映射" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
if [[ "${AUTO_YES}" -ne 1 && "${DRY_RUN}" -ne 1 ]]; then
|
if [[ "${AUTO_YES}" -ne 1 && "${DRY_RUN}" -ne 1 ]]; then
|
||||||
say "发布确认"
|
say "发布确认"
|
||||||
@@ -158,6 +183,8 @@ say "生成发布清单"
|
|||||||
echo "root=${ROOT}"
|
echo "root=${ROOT}"
|
||||||
echo "git_head=$(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
echo "git_head=$(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||||
echo "git_branch=$(git -C "${ROOT}" branch --show-current 2>/dev/null || echo detached)"
|
echo "git_branch=$(git -C "${ROOT}" branch --show-current 2>/dev/null || echo detached)"
|
||||||
|
echo "memind_git_head=${MEMIND_GIT_HEAD}"
|
||||||
|
echo "memind_git_branch=${MEMIND_GIT_BRANCH}"
|
||||||
echo "git_status_begin"
|
echo "git_status_begin"
|
||||||
git -C "${ROOT}" status --short --branch || true
|
git -C "${ROOT}" status --short --branch || true
|
||||||
echo "git_status_end"
|
echo "git_status_end"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
@@ -220,6 +221,17 @@ export function createAdminApp(services) {
|
|||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adminApi.get('/analytics/sso', requireAdmin, async (req, res) => {
|
||||||
|
const sharedSecret = process.env.MEMIND_UMAMI_SSO_SECRET?.trim();
|
||||||
|
if (!sharedSecret) return res.status(503).json({ message: '未配置 Umami 单点登录密钥' });
|
||||||
|
const config = loadMindSpaceConfig ? await loadMindSpaceConfig(pool) : null;
|
||||||
|
const baseUrl = String(config?.analytics?.analyticsUrl || process.env.UMAMI_URL || 'http://127.0.0.1:3100').replace(/\/$/, '');
|
||||||
|
const username = process.env.UMAMI_SSO_USERNAME?.trim() || 'admin';
|
||||||
|
const encoded = Buffer.from(JSON.stringify({ username, exp: Math.floor(Date.now() / 1000) + 60, nonce: crypto.randomUUID() })).toString('base64url');
|
||||||
|
const signature = crypto.createHmac('sha256', sharedSecret).update(encoded).digest('base64url');
|
||||||
|
res.json({ url: `${baseUrl}/auth/memind?ticket=${encoded}.${signature}` });
|
||||||
|
});
|
||||||
|
|
||||||
adminApi.get('/users', requireAdmin, async (req, res) => {
|
adminApi.get('/users', requireAdmin, async (req, res) => {
|
||||||
const result = await userAuth.listUsers({
|
const result = await userAuth.listUsers({
|
||||||
page: Number(req.query.page) || 1,
|
page: Number(req.query.page) || 1,
|
||||||
@@ -325,6 +337,7 @@ export function createAdminApp(services) {
|
|||||||
if (!updateMindSpaceConfig) return res.status(503).json({ message: 'MindSpace 配置未启用' });
|
if (!updateMindSpaceConfig) return res.status(503).json({ message: 'MindSpace 配置未启用' });
|
||||||
const result = await updateMindSpaceConfig(pool, {
|
const result = await updateMindSpaceConfig(pool, {
|
||||||
publicPageLimit: req.body?.publicPageLimit,
|
publicPageLimit: req.body?.publicPageLimit,
|
||||||
|
analytics: req.body?.analytics,
|
||||||
});
|
});
|
||||||
res.json({ config: result });
|
res.json({ config: result });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { UsersPage } from './admin/pages/UsersPage';
|
|||||||
import { WechatPage } from './admin/pages/WechatPage';
|
import { WechatPage } from './admin/pages/WechatPage';
|
||||||
import { AssetGatewayPage } from './admin/pages/AssetGatewayPage';
|
import { AssetGatewayPage } from './admin/pages/AssetGatewayPage';
|
||||||
import { BlockedWordsPage } from './admin/pages/BlockedWordsPage';
|
import { BlockedWordsPage } from './admin/pages/BlockedWordsPage';
|
||||||
|
import { AnalyticsConfigPage } from './admin/pages/AnalyticsConfigPage';
|
||||||
import { SkillRuntimePage } from './admin/pages/SkillRuntimePage';
|
import { SkillRuntimePage } from './admin/pages/SkillRuntimePage';
|
||||||
import { defaultHomePath } from './lib/routes';
|
import { defaultHomePath } from './lib/routes';
|
||||||
import { OpsLayout } from './ops/components/OpsLayout';
|
import { OpsLayout } from './ops/components/OpsLayout';
|
||||||
@@ -122,6 +123,7 @@ function AdminApp({ user, onLogout }: { user: PortalUser; onLogout: () => void }
|
|||||||
<Route path="system-tests" element={<SystemTestsPage />} />
|
<Route path="system-tests" element={<SystemTestsPage />} />
|
||||||
<Route path="policies" element={<PoliciesPage />} />
|
<Route path="policies" element={<PoliciesPage />} />
|
||||||
<Route path="mindspace" element={<MindSpacePage />} />
|
<Route path="mindspace" element={<MindSpacePage />} />
|
||||||
|
<Route path="analytics" element={<AnalyticsConfigPage />} />
|
||||||
<Route path="memory-v2" element={<MemoryV2Page />} />
|
<Route path="memory-v2" element={<MemoryV2Page />} />
|
||||||
<Route path="mindsearch" element={<MindSearchPage />} />
|
<Route path="mindsearch" element={<MindSearchPage />} />
|
||||||
<Route path="skill-runtime" element={<SkillRuntimePage />} />
|
<Route path="skill-runtime" element={<SkillRuntimePage />} />
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const NAV_SECTIONS: NavSection[] = [
|
|||||||
items: [
|
items: [
|
||||||
{ to: '/wechat', label: '服务号' },
|
{ to: '/wechat', label: '服务号' },
|
||||||
{ to: '/mindspace', label: 'MindSpace 配置' },
|
{ to: '/mindspace', label: 'MindSpace 配置' },
|
||||||
|
{ to: '/analytics', label: 'Analytics 配置' },
|
||||||
{ to: '/memory-v2', label: 'Memory V2' },
|
{ to: '/memory-v2', label: 'Memory V2' },
|
||||||
{ to: '/mindsearch', label: 'MindSearch' },
|
{ to: '/mindsearch', label: 'MindSearch' },
|
||||||
{ to: '/skill-runtime', label: 'Skill Runtime' },
|
{ to: '/skill-runtime', label: 'Skill Runtime' },
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { useCallback, useEffect, useState, type FormEvent } from 'react';
|
||||||
|
import { getMindSpaceAdminConfig, getUmamiSsoUrl, updateMindSpaceAdminConfig } from '../../api/client';
|
||||||
|
|
||||||
|
const initial = {
|
||||||
|
enabled: false,
|
||||||
|
websiteId: '',
|
||||||
|
analyticsUrl: 'http://127.0.0.1:3100',
|
||||||
|
domains: '127.0.0.1,localhost',
|
||||||
|
idSecretConfigured: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AnalyticsConfigPage() {
|
||||||
|
const [config, setConfig] = useState(initial);
|
||||||
|
const [secret, setSecret] = useState('');
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [message, setMessage] = useState<string | null>(null);
|
||||||
|
const [opening, setOpening] = useState(false);
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await getMindSpaceAdminConfig();
|
||||||
|
setConfig({ ...initial, ...result.analytics });
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : '加载 Analytics 配置失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => { void load(); }, [load]);
|
||||||
|
|
||||||
|
const save = async (event: FormEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setBusy(true);
|
||||||
|
setError(null);
|
||||||
|
setMessage(null);
|
||||||
|
try {
|
||||||
|
const result = await updateMindSpaceAdminConfig({
|
||||||
|
analytics: { enabled: config.enabled, websiteId: config.websiteId, analyticsUrl: config.analyticsUrl, domains: config.domains, ...(secret.trim() ? { idSecret: secret.trim() } : {}) },
|
||||||
|
});
|
||||||
|
setConfig({ ...initial, ...result.analytics });
|
||||||
|
setSecret('');
|
||||||
|
setMessage('Analytics 配置已保存;密钥不会在后台回显。');
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : '保存 Analytics 配置失败');
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openUmami = async () => {
|
||||||
|
setOpening(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
window.location.assign(await getUmamiSsoUrl());
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : '无法打开 Umami');
|
||||||
|
setOpening(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <div className="admin-page">
|
||||||
|
<div className="admin-page-head"><h2>Analytics 配置</h2><p className="muted">配置本地 Memind 生成页面的 Umami 统计。默认只连接本机 Umami,不涉及生产环境。</p></div>
|
||||||
|
{error && <p className="banner banner-error">{error}</p>}
|
||||||
|
{message && <p className="banner banner-info">{message}</p>}
|
||||||
|
<section className="admin-card">
|
||||||
|
<h2>本地 Umami</h2>
|
||||||
|
<div className="admin-actions" style={{ marginBottom: 16 }}><button type="button" className="send-btn" onClick={() => void openUmami()} disabled={opening || loading}>{opening ? '正在进入 Umami...' : '打开 Umami 分析后台'}</button></div>
|
||||||
|
<form className="admin-form" onSubmit={save}>
|
||||||
|
<label className="admin-form-row"><span>启用页面统计</span><input type="checkbox" checked={config.enabled} disabled={loading || busy} onChange={(e) => setConfig({ ...config, enabled: e.target.checked })} /></label>
|
||||||
|
<label className="admin-form-row"><span>Website ID</span><input value={config.websiteId} disabled={loading || busy} onChange={(e) => setConfig({ ...config, websiteId: e.target.value })} placeholder="Umami Website ID" required={config.enabled} /></label>
|
||||||
|
<label className="admin-form-row"><span>Umami 地址</span><input value={config.analyticsUrl} disabled={loading || busy} onChange={(e) => setConfig({ ...config, analyticsUrl: e.target.value })} placeholder="http://127.0.0.1:3100" /></label>
|
||||||
|
<label className="admin-form-row"><span>统计域名</span><input value={config.domains} disabled={loading || busy} onChange={(e) => setConfig({ ...config, domains: e.target.value })} placeholder="127.0.0.1,localhost" /></label>
|
||||||
|
<label className="admin-form-row"><span>匿名化密钥 {config.idSecretConfigured ? '(已配置,留空保持不变)' : ''}</span><input type="password" value={secret} disabled={loading || busy} onChange={(e) => setSecret(e.target.value)} placeholder={config.idSecretConfigured ? '留空保持当前密钥' : '输入本地随机密钥'} autoComplete="new-password" /></label>
|
||||||
|
<div className="admin-actions"><button type="submit" className="send-btn" disabled={busy || loading}>{busy ? '保存中...' : '保存配置'}</button><button type="button" className="ghost-btn" onClick={() => void load()} disabled={busy}>重新加载</button></div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ type ModelBackendKey = 'mem0' | 'letta' | 'langgraph';
|
|||||||
|
|
||||||
type CapabilityKey =
|
type CapabilityKey =
|
||||||
| 'candidateMemory'
|
| 'candidateMemory'
|
||||||
|
| 'runtimeControl'
|
||||||
| 'policy'
|
| 'policy'
|
||||||
| 'retriever'
|
| 'retriever'
|
||||||
| 'lifecycle'
|
| 'lifecycle'
|
||||||
@@ -42,7 +43,7 @@ type CapabilityKey =
|
|||||||
type CapabilityField = {
|
type CapabilityField = {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: 'boolean' | 'number' | 'select';
|
type: 'boolean' | 'number' | 'select' | 'text';
|
||||||
options?: Array<{ value: string; label: string }>;
|
options?: Array<{ value: string; label: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,6 +73,34 @@ const CAPABILITIES: Array<{
|
|||||||
{ key: 'persistenceEnabled', label: '持久化到 MySQL', type: 'boolean' },
|
{ key: 'persistenceEnabled', label: '持久化到 MySQL', type: 'boolean' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'runtimeControl',
|
||||||
|
label: 'Runtime 控制',
|
||||||
|
icon: 'A',
|
||||||
|
description: '独立于 Memory V2 总开关,控制 Agent 读取、注入、晋升与后续生命周期能力。',
|
||||||
|
fields: [
|
||||||
|
{ key: 'agentResolveEnabled', label: '允许 Agent Resolve', type: 'boolean' },
|
||||||
|
{ key: 'agentInjectionMode', label: 'Agent 注入模式', type: 'select', options: [
|
||||||
|
{ value: 'off', label: 'Off · 关闭' },
|
||||||
|
{ value: 'shadow', label: 'Shadow · 只观察不注入' },
|
||||||
|
{ value: 'canary', label: 'Canary · 灰度注入' },
|
||||||
|
{ value: 'active', label: 'Active · 正式注入' },
|
||||||
|
] },
|
||||||
|
{ key: 'agentCanaryUserIds', label: 'Canary 用户 ID(逗号分隔)', type: 'text' },
|
||||||
|
{ key: 'agentResolveLimit', label: 'Agent 召回条数', type: 'number' },
|
||||||
|
{ key: 'agentResolveTimeoutMs', label: 'Agent Resolve 超时 Ms', type: 'number' },
|
||||||
|
{ key: 'promotionEnabled', label: '允许候选晋升正式记忆', type: 'boolean' },
|
||||||
|
{ key: 'compactionV2Enabled', label: '启用 Compact V2', type: 'boolean' },
|
||||||
|
{ key: 'reflectionEnabled', label: '启用 Reflection', type: 'boolean' },
|
||||||
|
{ key: 'lifecycleWorkerEnabled', label: '启用 Lifecycle Worker', type: 'boolean' },
|
||||||
|
{ key: 'lifecycleRolloutMode', label: 'Lifecycle 灰度模式', type: 'select', options: [
|
||||||
|
{ value: 'off', label: 'Off · 关闭' },
|
||||||
|
{ value: 'canary', label: 'Canary · 仅指定用户' },
|
||||||
|
{ value: 'active', label: 'Active · 全量' },
|
||||||
|
] },
|
||||||
|
{ key: 'lifecycleRolloutUserIds', label: 'Lifecycle 用户 ID(逗号分隔)', type: 'text' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'policy',
|
key: 'policy',
|
||||||
label: 'Policy',
|
label: 'Policy',
|
||||||
@@ -93,6 +122,12 @@ const CAPABILITIES: Array<{
|
|||||||
fields: [
|
fields: [
|
||||||
{ key: 'enabled', label: '启用组合召回', type: 'boolean' },
|
{ key: 'enabled', label: '启用组合召回', type: 'boolean' },
|
||||||
{ key: 'episodicEnabled', label: '事件记忆', type: 'boolean' },
|
{ key: 'episodicEnabled', label: '事件记忆', type: 'boolean' },
|
||||||
|
{ key: 'episodicMode', label: '历史会话召回模式', type: 'select', options: [
|
||||||
|
{ value: 'off', label: 'Off · 关闭' },
|
||||||
|
{ value: 'canary', label: 'Canary · 仅指定用户' },
|
||||||
|
{ value: 'active', label: 'Active · 全量' },
|
||||||
|
] },
|
||||||
|
{ key: 'episodicCanaryUserIds', label: '历史会话 Canary 用户 ID(逗号分隔)', type: 'text' },
|
||||||
{ key: 'semanticEnabled', label: '语义记忆', type: 'boolean' },
|
{ key: 'semanticEnabled', label: '语义记忆', type: 'boolean' },
|
||||||
{ key: 'preferenceEnabled', label: '用户偏好', type: 'boolean' },
|
{ key: 'preferenceEnabled', label: '用户偏好', type: 'boolean' },
|
||||||
{ key: 'goalEnabled', label: '活跃目标', type: 'boolean' },
|
{ key: 'goalEnabled', label: '活跃目标', type: 'boolean' },
|
||||||
@@ -315,8 +350,9 @@ function defaultConfig(): MemoryV2AdminConfig {
|
|||||||
fallbackRoute: 'agent_orchestration',
|
fallbackRoute: 'agent_orchestration',
|
||||||
},
|
},
|
||||||
candidateMemory: { enabled: false, mode: 'active', minImportance: '0.7', minConfidence: '0.8', maxPending: '500', persistenceEnabled: false },
|
candidateMemory: { enabled: false, mode: 'active', minImportance: '0.7', minConfidence: '0.8', maxPending: '500', persistenceEnabled: false },
|
||||||
|
runtimeControl: { agentResolveEnabled: false, agentInjectionMode: 'off', agentCanaryUserIds: '', agentResolveLimit: '3', agentResolveTimeoutMs: '1200', promotionEnabled: false, compactionV2Enabled: false, reflectionEnabled: false, lifecycleWorkerEnabled: false, lifecycleRolloutMode: 'off', lifecycleRolloutUserIds: '' },
|
||||||
policy: { enabled: false, saveExplicit: true, rejectSensitive: true, requireEvidence: true, retentionDays: '365' },
|
policy: { enabled: false, saveExplicit: true, rejectSensitive: true, requireEvidence: true, retentionDays: '365' },
|
||||||
retriever: { enabled: false, episodicEnabled: true, semanticEnabled: true, preferenceEnabled: true, goalEnabled: true, limit: '12', tokenBudget: '1800', timeoutMs: '1200' },
|
retriever: { enabled: false, episodicEnabled: true, episodicMode: 'off', episodicCanaryUserIds: '', semanticEnabled: true, preferenceEnabled: true, goalEnabled: true, limit: '12', tokenBudget: '1800', timeoutMs: '1200' },
|
||||||
lifecycle: { enabled: false, dedupeEnabled: true, conflictReview: true, decayEnabled: false, forgettingEnabled: true, compactIntervalHours: '24' },
|
lifecycle: { enabled: false, dedupeEnabled: true, conflictReview: true, decayEnabled: false, forgettingEnabled: true, compactIntervalHours: '24' },
|
||||||
persona: { enabled: false, provider: 'none', shadowMode: true, maxTokens: '400', cacheTtlSeconds: '300' },
|
persona: { enabled: false, provider: 'none', shadowMode: true, maxTokens: '400', cacheTtlSeconds: '300' },
|
||||||
graph: { enabled: false, provider: 'postgres', maxDepth: '2', relationLimit: '20' },
|
graph: { enabled: false, provider: 'postgres', maxDepth: '2', relationLimit: '20' },
|
||||||
@@ -339,6 +375,7 @@ function normalizeConfig(config?: Partial<MemoryV2AdminConfig> | null): MemoryV2
|
|||||||
global: { ...base.global, ...(config?.global ?? {}) },
|
global: { ...base.global, ...(config?.global ?? {}) },
|
||||||
chatIntentRouter: { ...base.chatIntentRouter, ...(config?.chatIntentRouter ?? {}) },
|
chatIntentRouter: { ...base.chatIntentRouter, ...(config?.chatIntentRouter ?? {}) },
|
||||||
candidateMemory: { ...base.candidateMemory, ...(config?.candidateMemory ?? {}) },
|
candidateMemory: { ...base.candidateMemory, ...(config?.candidateMemory ?? {}) },
|
||||||
|
runtimeControl: { ...base.runtimeControl, ...(config?.runtimeControl ?? {}) },
|
||||||
policy: { ...base.policy, ...(config?.policy ?? {}) },
|
policy: { ...base.policy, ...(config?.policy ?? {}) },
|
||||||
retriever: { ...base.retriever, ...(config?.retriever ?? {}) },
|
retriever: { ...base.retriever, ...(config?.retriever ?? {}) },
|
||||||
lifecycle: { ...base.lifecycle, ...(config?.lifecycle ?? {}) },
|
lifecycle: { ...base.lifecycle, ...(config?.lifecycle ?? {}) },
|
||||||
@@ -933,6 +970,18 @@ export function MemoryV2Page() {
|
|||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (field.type === 'text') {
|
||||||
|
return (
|
||||||
|
<label key={field.key}>
|
||||||
|
<span>{field.label}</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={String(section[field.key] ?? '')}
|
||||||
|
onChange={updateCapabilityValue(capability.key, field.key)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<label key={field.key}>
|
<label key={field.key}>
|
||||||
<span>{field.label}</span>
|
<span>{field.label}</span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getMindSpaceAdminConfig, updateMindSpaceAdminConfig } from '../../api/c
|
|||||||
import type { MindSpaceAdminConfig } from '../../types';
|
import type { MindSpaceAdminConfig } from '../../types';
|
||||||
|
|
||||||
function safeConfig(config: MindSpaceAdminConfig | null): MindSpaceAdminConfig {
|
function safeConfig(config: MindSpaceAdminConfig | null): MindSpaceAdminConfig {
|
||||||
return config ?? { publicPageLimit: 10 };
|
return config ?? { publicPageLimit: 10, analytics: { enabled: false, websiteId: '', analyticsUrl: 'http://127.0.0.1:3100', domains: '127.0.0.1,localhost', idSecretConfigured: false } };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MindSpacePage() {
|
export function MindSpacePage() {
|
||||||
|
|||||||
+7
-1
@@ -30,6 +30,7 @@ import type {
|
|||||||
PolicyMap,
|
PolicyMap,
|
||||||
PortalUser,
|
PortalUser,
|
||||||
MindSpaceAdminConfig,
|
MindSpaceAdminConfig,
|
||||||
|
MindSpaceAnalyticsUpdate,
|
||||||
MemoryV2AdminConfig,
|
MemoryV2AdminConfig,
|
||||||
MemoryV2AdminConfigResponse,
|
MemoryV2AdminConfigResponse,
|
||||||
MemoryV2ModelApiType,
|
MemoryV2ModelApiType,
|
||||||
@@ -319,8 +320,13 @@ export async function getMindSpaceAdminConfig(): Promise<MindSpaceAdminConfig> {
|
|||||||
return result.config;
|
return result.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUmamiSsoUrl(): Promise<string> {
|
||||||
|
const result = await portalFetch<{ url: string }>('/admin-api/analytics/sso');
|
||||||
|
return result.url;
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateMindSpaceAdminConfig(
|
export async function updateMindSpaceAdminConfig(
|
||||||
payload: Partial<MindSpaceAdminConfig>,
|
payload: Partial<MindSpaceAdminConfig> & { analytics?: MindSpaceAnalyticsUpdate },
|
||||||
): Promise<MindSpaceAdminConfig> {
|
): Promise<MindSpaceAdminConfig> {
|
||||||
const result = await portalFetch<{ config: MindSpaceAdminConfig }>('/admin-api/mindspace/config', {
|
const result = await portalFetch<{ config: MindSpaceAdminConfig }>('/admin-api/mindspace/config', {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
|
|||||||
@@ -281,6 +281,21 @@ export type WechatScheduleLlmConfig = {
|
|||||||
|
|
||||||
export type MindSpaceAdminConfig = {
|
export type MindSpaceAdminConfig = {
|
||||||
publicPageLimit: number;
|
publicPageLimit: number;
|
||||||
|
analytics: {
|
||||||
|
enabled: boolean;
|
||||||
|
websiteId: string;
|
||||||
|
analyticsUrl: string;
|
||||||
|
domains: string;
|
||||||
|
idSecretConfigured: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MindSpaceAnalyticsUpdate = {
|
||||||
|
enabled?: boolean;
|
||||||
|
websiteId?: string;
|
||||||
|
analyticsUrl?: string;
|
||||||
|
domains?: string;
|
||||||
|
idSecret?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MemoryV2AdminSection = Record<string, string | boolean>;
|
export type MemoryV2AdminSection = Record<string, string | boolean>;
|
||||||
@@ -297,6 +312,7 @@ export type MemoryV2AdminConfig = {
|
|||||||
};
|
};
|
||||||
chatIntentRouter: MemoryV2AdminSection;
|
chatIntentRouter: MemoryV2AdminSection;
|
||||||
candidateMemory: MemoryV2AdminSection;
|
candidateMemory: MemoryV2AdminSection;
|
||||||
|
runtimeControl: MemoryV2AdminSection;
|
||||||
policy: MemoryV2AdminSection;
|
policy: MemoryV2AdminSection;
|
||||||
retriever: MemoryV2AdminSection;
|
retriever: MemoryV2AdminSection;
|
||||||
lifecycle: MemoryV2AdminSection;
|
lifecycle: MemoryV2AdminSection;
|
||||||
|
|||||||
Reference in New Issue
Block a user