新增: u-claw 集成
This commit is contained in:
@@ -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' },
|
||||
]
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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 渲染行,此前漏入索引
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -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 / 模型)',
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user