- 全局样式类(df-sec-h/df-toast族)替换散装实现,主题token去硬编码兜底 - 拆分子组件:ai-chat/queue-panel(队列时钟与编辑随迁)、 conversation-sidebar/ConversationList、settings/provider-panel - 父组件死副本清理(队列编辑/时钟/键位表,TS6133清零)并补 fill-draft父侧转发(重构半成品断链修复) - 验收:vue-tsc EXIT 0
130 lines
5.4 KiB
Vue
130 lines
5.4 KiB
Vue
<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; }
|
|
/* 定宽输入补 max-width,窄窗收缩防横向溢出 */
|
|
.uclaw-input { width: 320px; max-width: 100%; font-family: var(--df-font-mono, monospace); }
|
|
/* 原 var(--df-text-tertiary) 为幽灵 token(全 styles 未定义,渲染成继承色),改用已定义的弱化色 */
|
|
.uclaw-hint {
|
|
font-size: 11px;
|
|
color: var(--df-text-dim);
|
|
line-height: 1.6;
|
|
margin-top: 4px;
|
|
white-space: pre-line;
|
|
}
|
|
.actions { display: flex; gap: 8px; margin-top: 8px; }
|
|
</style>
|