新增: u-claw 集成

This commit is contained in:
lxy
2026-10-05 14:59:11 +08:00
parent d4d3a8deaf
commit 7c76ee6a60
21 changed files with 835 additions and 4 deletions
+2
View File
@@ -50,6 +50,7 @@
// knowledge 知识库(auto_extract/trigger_mode/embedding_*)
// performance 性能(global/per-conv concurrency/max-iterations/max-retries)
// security 安全(allowed_dirs)
// integrations 集成(飞书渠道凭证)
// advanced 高级(autoExecuteMode/logLevel + 连接)
// ============================================================
@@ -73,6 +74,7 @@ const categories: NavCategory[] = [
{ value: 'knowledge', icon: '📚', labelKey: 'settings.navKnowledge' },
{ value: 'performance', icon: '⚡', labelKey: 'settings.navPerformance' },
{ value: 'security', icon: '🔒', labelKey: 'settings.navSecurity' },
{ value: 'integrations', icon: '🔌', labelKey: 'settings.navIntegrations' },
{ value: 'advanced', icon: '⚙️', labelKey: 'settings.navAdvanced' },
]
+127
View File
@@ -0,0 +1,127 @@
<template>
<!-- ═══ u-claw 集成(ask_uclaw 工具:经 u-relay commander 协议委托常驻 Claude Code 网关) ═══ -->
<section class="panel">
<div class="panel-header">
<h2>{{ $t('settings.panelUclaw') }}</h2>
</div>
<div class="uclaw-settings">
<SettingRow :label="$t('settings.labelUclawRelayUrl')" :desc="$t('settings.descUclawRelayUrl')">
<input
v-model="form.relay_url"
type="text"
class="setting-input uclaw-input"
:placeholder="$t('settings.phUclawRelayUrl')"
autocomplete="off"
spellcheck="false"
/>
</SettingRow>
<SettingRow :label="$t('settings.labelUclawChannel')" :desc="$t('settings.descUclawChannel')">
<input
v-model="form.channel_id"
type="text"
class="setting-input uclaw-input"
:placeholder="$t('settings.phUclawChannel')"
autocomplete="off"
spellcheck="false"
/>
</SettingRow>
<SettingRow :label="$t('settings.labelUclawToken')" :desc="$t('settings.descUclawToken')">
<input
v-model="form.token"
:type="secretVisible ? 'text' : 'password'"
class="setting-input uclaw-input"
autocomplete="new-password"
/>
<!-- 掩码显隐复用 Provider 密钥的「显示/隐藏」文案,语义一致不另造键 -->
<button class="btn btn-ghost btn-sm" type="button" @click="secretVisible = !secretVisible">
{{ secretVisible ? $t('settings.apiKeyHide') : $t('settings.apiKeyShow') }}
</button>
</SettingRow>
<div class="uclaw-hint">{{ $t('settings.descUclawHint') }}</div>
<div class="actions">
<button class="btn btn-primary btn-sm" :disabled="saving" @click="save">
{{ $t('settings.uclawSave') }}
</button>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppSettingsStore } from '@/stores/appSettings'
import SettingRow from './SettingRow.vue'
// ============================================================
// u-claw 集成配置 — ask_uclaw 工具的连接三要素(relay URL / channel / token)
// ------------------------------------------------------------
// 持久化走 appSettings KV,三键独立落库(与后端 tools/uclaw.rs load_config 同键名):
// uclaw_relay_url / uclaw_channel_id / uclaw_token
// 与 FeishuPanel 的单键整体存法不同:后端 load_config 逐键读,前端逐键写保证两端同形。
// store set() 会 JSON.stringify 落库,后端 get_string_norm 归一去引号(两端契约已对齐)。
// 三项全空 = 未配置(默认关),ask_uclaw 工具会返回明确错误;部分填写保存时警告但不阻止
// (半份配置无害——后端三项校验任一空即拒绝执行)。
// ============================================================
const KV_RELAY_URL = 'uclaw_relay_url'
const KV_CHANNEL_ID = 'uclaw_channel_id'
const KV_TOKEN = 'uclaw_token'
// 接共享 toast 通道(Settings.vue showToast),与 FeishuPanel 同范式
const emit = defineEmits<{ (e: 'toast', msg: string, type: 'error' | 'warning' | 'info' | 'success'): void }>()
const { t } = useI18n()
const appSettings = useAppSettingsStore()
const form = reactive({ relay_url: '', channel_id: '', token: '' })
const secretVisible = ref(false)
const saving = ref(false)
onMounted(() => {
// KV 值由 store 启动 loadAll 统一 JSON.parse 进缓存,此处同步读;
// 逐字段回退默认,防历史脏数据缺字段时 v-model 绑到 undefined
form.relay_url = typeof appSettings.get<string>(KV_RELAY_URL, '') === 'string' ? appSettings.get<string>(KV_RELAY_URL, '') : ''
form.channel_id = typeof appSettings.get<string>(KV_CHANNEL_ID, '') === 'string' ? appSettings.get<string>(KV_CHANNEL_ID, '') : ''
form.token = typeof appSettings.get<string>(KV_TOKEN, '') === 'string' ? appSettings.get<string>(KV_TOKEN, '') : ''
})
/** 保存:部分填写警告(不阻止);三键独立落库(与后端 load_config 同键) */
async function save() {
const filled = [form.relay_url.trim(), form.channel_id.trim(), form.token.trim()].filter(Boolean).length
if (filled > 0 && filled < 3) {
emit('toast', t('settings.uclawValidateIncomplete'), 'warning')
return
}
saving.value = true
try {
await appSettings.set(KV_RELAY_URL, form.relay_url.trim())
await appSettings.set(KV_CHANNEL_ID, form.channel_id.trim())
await appSettings.set(KV_TOKEN, form.token.trim())
emit('toast', t('settings.uclawSaveOk'), 'success')
} catch (e) {
console.error('[UCLAW-CONFIG] 保存失败:', e)
emit('toast', t('settings.toastSaveFail', { msg: String(e) }), 'error')
} finally {
saving.value = false
}
}
</script>
<style scoped>
/* panel/panel-header/btn 系列/setting-input 均在 settings.css 全局,此处仅本面板特有样式 */
.uclaw-settings { display: flex; flex-direction: column; gap: 4px; }
.uclaw-input { width: 320px; font-family: var(--df-font-mono, monospace); }
.uclaw-hint {
font-size: 11px;
color: var(--df-text-tertiary);
line-height: 1.6;
margin-top: 4px;
white-space: pre-line;
}
.actions { display: flex; gap: 8px; margin-top: 8px; }
</style>
+14
View File
@@ -18,6 +18,7 @@ export type SettingsCategory =
| 'knowledge'
| 'performance'
| 'security'
| 'integrations'
| 'advanced'
/** 搜索索引项:category 所属分类 + labelKey/descKey(i18n key) + key 唯一标识 */
@@ -42,6 +43,7 @@ export interface SettingIndexEntry {
* performance 性能(global-concurrency/per-conv-concurrency/
* max-iterations/max-retries)
* security 安全(allowed_dirs)
* integrations 集成(飞书渠道凭证)
* advanced 高级(autoExecuteMode/logLevel + 连接)
*/
export const SETTINGS_INDEX: SettingIndexEntry[] = [
@@ -83,6 +85,18 @@ export const SETTINGS_INDEX: SettingIndexEntry[] = [
{ category: 'security', key: 'scriptWhitelist', labelKey: 'settings.labelScriptWhitelist', descKey: 'settings.descScriptWhitelist' },
{ category: 'security', key: 'scriptBlacklist', labelKey: 'settings.labelScriptBlacklist', descKey: 'settings.descScriptBlacklist' },
// ===== 集成 =====
{ category: 'integrations', key: 'feishu', labelKey: 'settings.panelFeishu' },
{ category: 'integrations', key: 'feishuEnabled', labelKey: 'settings.labelFeishuEnabled', descKey: 'settings.descFeishuEnabled' },
{ category: 'integrations', key: 'feishuAppId', labelKey: 'settings.labelFeishuAppId', descKey: 'settings.descFeishuAppId' },
{ category: 'integrations', key: 'feishuAppSecret', labelKey: 'settings.labelFeishuAppSecret', descKey: 'settings.descFeishuAppSecret' },
{ category: 'integrations', key: 'feishuWhitelist', labelKey: 'settings.labelFeishuWhitelist', descKey: 'settings.descFeishuWhitelist' },
// u-claw 集成(ask_uclaw 工具三键配置)
{ category: 'integrations', key: 'uclaw', labelKey: 'settings.panelUclaw' },
{ category: 'integrations', key: 'uclawRelayUrl', labelKey: 'settings.labelUclawRelayUrl', descKey: 'settings.descUclawRelayUrl' },
{ category: 'integrations', key: 'uclawChannel', labelKey: 'settings.labelUclawChannel', descKey: 'settings.descUclawChannel' },
{ category: 'integrations', key: 'uclawToken', labelKey: 'settings.labelUclawToken', descKey: 'settings.descUclawToken' },
// ===== 高级 =====
// F-#97:autoExecute(boolean) → autoExecuteMode(low/medium/all 三档),key 同步更名
// dataDir(应用数据目录):AdvancedSection.vue 渲染行,此前漏入索引
+33
View File
@@ -9,6 +9,7 @@ export default {
navKnowledge: 'Knowledge',
navPerformance: 'Performance',
navSecurity: 'Security',
navIntegrations: 'Integrations',
navAdvanced: 'Advanced',
// ===== Settings search (phase 4) =====
@@ -254,6 +255,38 @@ export default {
embeddingProviderNone: 'Not selected',
phEmbeddingModel: 'embedding-3',
// ===== Integrations: Feishu (built-in Feishu channel, WebSocket to the Feishu Open Platform) =====
panelFeishu: 'Feishu Integration',
labelFeishuEnabled: 'Enable Feishu integration',
descFeishuEnabled: 'Connect to the Feishu Open Platform over WebSocket to send and receive messages',
labelFeishuAppId: 'App ID',
descFeishuAppId: 'App ID of your Feishu app (Developer Console → Credentials & Basic Info)',
phFeishuAppId: 'cli_xxx',
labelFeishuAppSecret: 'App Secret',
descFeishuAppSecret: 'App Secret of your Feishu app, stored only in the local database and read/written with the config as a whole',
labelFeishuWhitelist: 'open_id whitelist',
descFeishuWhitelist: 'Comma-separated open_id list; leave empty to allow all users',
phFeishuWhitelist: 'ou_xxx, ou_yyy',
feishuSave: 'Save',
feishuSaveOk: 'Feishu integration settings saved',
// Submit guard: when enabled=true, either of id/secret empty blocks the write
feishuValidateIncomplete: 'Fill in App ID and App Secret to enable the Feishu integration',
// ===== Integrations: u-claw (ask_uclaw tool, u-relay commander protocol) =====
panelUclaw: 'u-claw Integration',
labelUclawRelayUrl: 'Relay URL',
descUclawRelayUrl: 'u-relay address (wss:// host is enough, /ws/commander auto-appended)',
phUclawRelayUrl: 'wss://relay.example.com',
labelUclawChannel: 'Channel ID',
descUclawChannel: 'Pairing channel, must match u-claw ws.channel_id',
phUclawChannel: 'u-ppt-dev-01',
labelUclawToken: 'Token',
descUclawToken: 'Relay auth token (same as u-relay U_RELAY_TOKEN)',
descUclawHint: 'Once configured, the AI can delegate tasks to u-claw (resident Claude Code gateway) via the ask_uclaw tool, with progress streamed back in real time. Leave all three empty to disable.',
uclawSave: 'Save',
uclawSaveOk: 'u-claw integration settings saved',
uclawValidateIncomplete: 'Fill in all three fields (Relay URL / Channel / Token), or leave all empty',
// ===== Toast =====
toastLoadProviderFail: 'Failed to load providers',
toastSaveIncomplete: 'Please fill in all fields (Name / Base URL / API Key / Model)',
+33
View File
@@ -9,6 +9,7 @@ export default {
navKnowledge: '知识库',
navPerformance: '性能',
navSecurity: '安全',
navIntegrations: '集成',
navAdvanced: '高级',
// ===== 设置搜索(阶段4) =====
@@ -253,6 +254,38 @@ export default {
embeddingProviderNone: '未选择',
phEmbeddingModel: 'embedding-3',
// ===== 集成:飞书(桌面端内置飞书渠道,WS 直连飞书开放平台) =====
panelFeishu: '飞书集成',
labelFeishuEnabled: '启用飞书集成',
descFeishuEnabled: '开启后桌面端经 WebSocket 直连飞书开放平台收发消息',
labelFeishuAppId: 'App ID',
descFeishuAppId: '飞书开放平台应用的 App ID(开发者后台 → 凭证与基础信息)',
phFeishuAppId: 'cli_xxx',
labelFeishuAppSecret: 'App Secret',
descFeishuAppSecret: '飞书开放平台应用的 App Secret,仅保存在本地数据库,随配置整体读写',
labelFeishuWhitelist: 'open_id 白名单',
descFeishuWhitelist: '逗号分隔的 open_id,留空表示放行所有用户',
phFeishuWhitelist: 'ou_xxx, ou_yyy',
feishuSave: '保存',
feishuSaveOk: '飞书集成配置已保存',
// 提交校验:enabled=true 时 id/secret 任一为空都拦在写库前
feishuValidateIncomplete: '启用飞书集成需先填写 App ID 和 App Secret',
// ===== 集成:u-claw(ask_uclaw 工具,u-relay commander 协议) =====
panelUclaw: 'u-claw 集成',
labelUclawRelayUrl: 'Relay 地址',
descUclawRelayUrl: 'u-relay 中继地址(wss:// 域名即可,自动补 /ws/commander)',
phUclawRelayUrl: 'wss://relay.example.com',
labelUclawChannel: 'Channel ID',
descUclawChannel: '配对通道,须与 u-claw 配置 ws.channel_id 一致',
phUclawChannel: 'u-ppt-dev-01',
labelUclawToken: 'Token',
descUclawToken: '中继鉴权令牌(与 u-relay 的 U_RELAY_TOKEN 同源)',
descUclawHint: '配置后 AI 可用 ask_uclaw 工具把任务委托给 u-claw(常驻 Claude Code 网关)执行,进度实时回传。三项留空 = 未启用。',
uclawSave: '保存',
uclawSaveOk: 'u-claw 集成配置已保存',
uclawValidateIncomplete: '三项(Relay 地址/Channel/Token)需完整填写,或全部留空',
// ===== Toast 提示 =====
toastLoadProviderFail: '加载提供商失败',
toastSaveIncomplete: '请填写完整(名称 / Base URL / API Key / 模型)',
+9
View File
@@ -69,6 +69,13 @@
@toast="showToast"
/>
<!-- 集成:飞书渠道凭证(WS 直连飞书开放平台,整体保存到 KV feishu-config)
+ u-claw(ask_uclaw 工具,u-relay commander 协议三键配置) -->
<template v-else-if="activeCategory === 'integrations'">
<FeishuPanel @toast="showToast" />
<UclawPanel @toast="showToast" />
</template>
<!-- 高级:autoExecuteMode/logLevel + 连接(暂留待定) -->
<template v-else-if="activeCategory === 'advanced'">
<AdvancedSection />
@@ -98,6 +105,8 @@ import PerformanceSection from '@/components/settings/PerformanceSection.vue'
import AdvancedSection from '@/components/settings/AdvancedSection.vue'
import KnowledgePanel from '@/components/settings/KnowledgePanel.vue'
import AllowedDirsPanel from '@/components/settings/AllowedDirsPanel.vue'
import FeishuPanel from '@/components/settings/FeishuPanel.vue'
import UclawPanel from '@/components/settings/UclawPanel.vue'
// SettingsImportExport 已移除(价值低,占用 UI 空间;跨设备同步建议直接复制 SQLite)
import { useAppSettingsStore } from '@/stores/appSettings'
import { aiApi } from '@/api'