Skip to content

Commit f7a3424

Browse files
author
linyuan.yang
committed
up
1 parent 801de3c commit f7a3424

7 files changed

Lines changed: 574 additions & 84 deletions

File tree

packages/admin/src/components/SessionConfigOverridesEditor.vue

Lines changed: 54 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script setup lang="ts">
2-
import { ref } from 'vue'
32
import { useI18n } from 'vue-i18n'
4-
import { SInput, STextarea, SSelect, SFormItem, SMultiSelect } from 'sbot-ui'
3+
import { SInput, STextarea, SSelect, SFormItem, SMultiSelect, SButton } from 'sbot-ui'
54
import { ApprovalTimeoutValue } from 'sbot.commons'
65
76
/**
@@ -45,10 +44,10 @@ const props = defineProps<{
4544
4645
const emit = defineEmits<{
4746
(e: 'update:modelValue', v: SessionOverrides): void
47+
(e: 'browse-path'): void
4848
}>()
4949
5050
const { t } = useI18n()
51-
const showAdvanced = ref(false)
5251
5352
function update<K extends keyof SessionOverrides>(key: K, val: SessionOverrides[K]) {
5453
emit('update:modelValue', { ...props.modelValue, [key]: val })
@@ -121,59 +120,58 @@ const fmtApprovalValue = (v: any) => v === ApprovalTimeoutValue.Allow ? t('chann
121120
</SFormItem>
122121

123122
<SFormItem :label="t('directory.path_label')" :hint="inheritLabel('workPath')">
124-
<SInput :model-value="modelValue.workPath ?? ''" type="text" @update:model-value="v => update('workPath', String(v).trim() ? String(v) : null)" />
123+
<div class="path-row">
124+
<SInput :model-value="modelValue.workPath ?? ''" type="text" class="path-input" @update:model-value="v => update('workPath', String(v).trim() ? String(v) : null)" />
125+
<SButton type="outline" size="sm" @click="emit('browse-path')">{{ t('directory.browse') }}</SButton>
126+
</div>
125127
</SFormItem>
126128

127-
<button class="advanced-toggle" type="button" @click="showAdvanced = !showAdvanced">
128-
{{ showAdvanced ? t('channels.section_advanced_hide') : t('channels.section_advanced_show', { n: 7 }) }}
129-
</button>
129+
<h4 class="form-section-title">{{ t('channels.section_advanced') }}</h4>
130130

131-
<template v-if="showAdvanced">
132-
<SFormItem :label="t('channels.stream_verbose')" :hint="inheritLabel('streamVerbose', fmtBool) || t('channels.stream_verbose_hint')">
133-
<SSelect :model-value="modelValue.streamVerbose === null ? '' : String(modelValue.streamVerbose)" @update:model-value="v => update('streamVerbose', v === '' ? null : v === 'true')">
134-
<option value="">{{ t('channels.use_channel_default') }}</option>
135-
<option value="true">{{ t('common.enabled') }}</option>
136-
<option value="false">{{ t('common.disabled') }}</option>
137-
</SSelect>
138-
</SFormItem>
139-
<SFormItem :label="t('settings.auto_approve_all')" :hint="inheritLabel('autoApproveAllTools', fmtBool) || t('settings.auto_approve_all_hint')">
140-
<SSelect :model-value="modelValue.autoApproveAllTools === null ? '' : String(modelValue.autoApproveAllTools)" @update:model-value="v => update('autoApproveAllTools', v === '' ? null : v === 'true')">
141-
<option value="">{{ t('channels.use_channel_default') }}</option>
142-
<option value="true">{{ t('common.enabled') }}</option>
143-
<option value="false">{{ t('common.disabled') }}</option>
144-
</SSelect>
145-
</SFormItem>
146-
<SFormItem :label="t('channels.approval_timeout')" :hint="inheritLabel('approvalTimeout') || t('channels.approval_timeout_hint')">
147-
<SInput :model-value="modelValue.approvalTimeout ?? ''" type="number" placeholder="0" @update:model-value="v => update('approvalTimeout', (v === '' || v === null || Number(v) <= 0) ? null : Number(v))" />
148-
</SFormItem>
149-
<SFormItem v-if="modelValue.approvalTimeout != null && modelValue.approvalTimeout > 0" :label="t('channels.approval_timeout_value')" :hint="inheritLabel('approvalTimeoutValue', fmtApprovalValue)">
150-
<SSelect :model-value="modelValue.approvalTimeoutValue ?? ''" @update:model-value="v => update('approvalTimeoutValue', v === '' ? null : v as ApprovalTimeoutValue)">
151-
<option value="">{{ t('channels.use_channel_default') }}</option>
152-
<option :value="ApprovalTimeoutValue.Deny">{{ t('channels.approval_timeout_value_deny') }}</option>
153-
<option :value="ApprovalTimeoutValue.Allow">{{ t('channels.approval_timeout_value_allow') }}</option>
154-
</SSelect>
155-
</SFormItem>
156-
<SFormItem :label="t('channels.ask_timeout')" :hint="inheritLabel('askTimeout') || t('channels.ask_timeout_hint')">
157-
<SInput :model-value="modelValue.askTimeout ?? ''" type="number" placeholder="0" @update:model-value="v => update('askTimeout', (v === '' || v === null || Number(v) <= 0) ? null : Number(v))" />
158-
</SFormItem>
159-
<SFormItem v-if="modelValue.askTimeout != null && modelValue.askTimeout > 0" :label="t('channels.ask_timeout_message')" :hint="inheritLabel('askTimeoutMessage') || t('channels.ask_timeout_message_hint')">
160-
<SInput :model-value="modelValue.askTimeoutMessage ?? ''" type="text" @update:model-value="v => update('askTimeoutMessage', String(v).trim() ? String(v) : null)" />
131+
<SFormItem :label="t('channels.stream_verbose')" :hint="inheritLabel('streamVerbose', fmtBool) || t('channels.stream_verbose_hint')">
132+
<SSelect :model-value="modelValue.streamVerbose === null ? '' : String(modelValue.streamVerbose)" @update:model-value="v => update('streamVerbose', v === '' ? null : v === 'true')">
133+
<option value="">{{ t('channels.use_channel_default') }}</option>
134+
<option value="true">{{ t('common.enabled') }}</option>
135+
<option value="false">{{ t('common.disabled') }}</option>
136+
</SSelect>
137+
</SFormItem>
138+
<SFormItem :label="t('settings.auto_approve_all')" :hint="inheritLabel('autoApproveAllTools', fmtBool) || t('settings.auto_approve_all_hint')">
139+
<SSelect :model-value="modelValue.autoApproveAllTools === null ? '' : String(modelValue.autoApproveAllTools)" @update:model-value="v => update('autoApproveAllTools', v === '' ? null : v === 'true')">
140+
<option value="">{{ t('channels.use_channel_default') }}</option>
141+
<option value="true">{{ t('common.enabled') }}</option>
142+
<option value="false">{{ t('common.disabled') }}</option>
143+
</SSelect>
144+
</SFormItem>
145+
<SFormItem :label="t('channels.approval_timeout')" :hint="inheritLabel('approvalTimeout') || t('channels.approval_timeout_hint')">
146+
<SInput :model-value="modelValue.approvalTimeout ?? ''" type="number" placeholder="0" @update:model-value="v => update('approvalTimeout', (v === '' || v === null || Number(v) <= 0) ? null : Number(v))" />
147+
</SFormItem>
148+
<SFormItem v-if="modelValue.approvalTimeout != null && modelValue.approvalTimeout > 0" :label="t('channels.approval_timeout_value')" :hint="inheritLabel('approvalTimeoutValue', fmtApprovalValue)">
149+
<SSelect :model-value="modelValue.approvalTimeoutValue ?? ''" @update:model-value="v => update('approvalTimeoutValue', v === '' ? null : v as ApprovalTimeoutValue)">
150+
<option value="">{{ t('channels.use_channel_default') }}</option>
151+
<option :value="ApprovalTimeoutValue.Deny">{{ t('channels.approval_timeout_value_deny') }}</option>
152+
<option :value="ApprovalTimeoutValue.Allow">{{ t('channels.approval_timeout_value_allow') }}</option>
153+
</SSelect>
154+
</SFormItem>
155+
<SFormItem :label="t('channels.ask_timeout')" :hint="inheritLabel('askTimeout') || t('channels.ask_timeout_hint')">
156+
<SInput :model-value="modelValue.askTimeout ?? ''" type="number" placeholder="0" @update:model-value="v => update('askTimeout', (v === '' || v === null || Number(v) <= 0) ? null : Number(v))" />
157+
</SFormItem>
158+
<SFormItem v-if="modelValue.askTimeout != null && modelValue.askTimeout > 0" :label="t('channels.ask_timeout_message')" :hint="inheritLabel('askTimeoutMessage') || t('channels.ask_timeout_message_hint')">
159+
<SInput :model-value="modelValue.askTimeoutMessage ?? ''" type="text" @update:model-value="v => update('askTimeoutMessage', String(v).trim() ? String(v) : null)" />
160+
</SFormItem>
161+
<SFormItem :label="t('channels.intent_model')" :hint="inheritLabel('intentModel', fmtModel) || t('channels.intent_model_hint')">
162+
<SSelect :model-value="modelValue.intentModel ?? '__default__'" @update:model-value="v => update('intentModel', v === '__default__' ? null : String(v))">
163+
<option value="__default__">{{ t('channels.use_channel_default') }}</option>
164+
<option value="">{{ t('common.not_use') }}</option>
165+
<option v-for="m in modelOptions" :key="m.id" :value="m.id">{{ m.label }}</option>
166+
</SSelect>
167+
</SFormItem>
168+
<template v-if="modelValue.intentModel">
169+
<SFormItem :label="t('channels.intent_threshold')" :hint="inheritLabel('intentThreshold') || t('channels.intent_threshold_hint')">
170+
<SInput :model-value="modelValue.intentThreshold ?? ''" type="number" placeholder="0.7" @update:model-value="v => update('intentThreshold', v === '' || v === null ? null : Number(v))" />
161171
</SFormItem>
162-
<SFormItem :label="t('channels.intent_model')" :hint="inheritLabel('intentModel', fmtModel) || t('channels.intent_model_hint')">
163-
<SSelect :model-value="modelValue.intentModel ?? '__default__'" @update:model-value="v => update('intentModel', v === '__default__' ? null : String(v))">
164-
<option value="__default__">{{ t('channels.use_channel_default') }}</option>
165-
<option value="">{{ t('common.not_use') }}</option>
166-
<option v-for="m in modelOptions" :key="m.id" :value="m.id">{{ m.label }}</option>
167-
</SSelect>
172+
<SFormItem :label="t('channels.intent_prompt')" :hint="inheritLabel('intentPrompt')">
173+
<STextarea :model-value="modelValue.intentPrompt ?? ''" :rows="4" :placeholder="t('channels.intent_prompt_placeholder')" @update:model-value="v => update('intentPrompt', String(v).trim() ? String(v) : null)" />
168174
</SFormItem>
169-
<template v-if="modelValue.intentModel">
170-
<SFormItem :label="t('channels.intent_threshold')" :hint="inheritLabel('intentThreshold') || t('channels.intent_threshold_hint')">
171-
<SInput :model-value="modelValue.intentThreshold ?? ''" type="number" placeholder="0.7" @update:model-value="v => update('intentThreshold', v === '' || v === null ? null : Number(v))" />
172-
</SFormItem>
173-
<SFormItem :label="t('channels.intent_prompt')" :hint="inheritLabel('intentPrompt')">
174-
<STextarea :model-value="modelValue.intentPrompt ?? ''" :rows="4" :placeholder="t('channels.intent_prompt_placeholder')" @update:model-value="v => update('intentPrompt', String(v).trim() ? String(v) : null)" />
175-
</SFormItem>
176-
</template>
177175
</template>
178176
</div>
179177
</template>
@@ -189,20 +187,11 @@ const fmtApprovalValue = (v: any) => v === ApprovalTimeoutValue.Allow ? t('chann
189187
text-transform: uppercase;
190188
letter-spacing: 0.04em;
191189
}
192-
.advanced-toggle {
193-
display: block;
194-
width: 100%;
195-
margin: var(--sui-sp-5) 0 var(--sui-sp-3);
196-
padding: var(--sui-sp-3) var(--sui-sp-4);
197-
background: var(--sui-bg-subtle);
198-
border: 1px dashed var(--sui-border);
199-
border-radius: var(--sui-radius-md);
200-
color: var(--sui-fg-secondary);
201-
font-size: var(--sui-fs-sm);
202-
cursor: pointer;
203-
text-align: center;
204-
transition: background var(--sui-transition-fast);
190+
.path-row {
191+
display: flex;
192+
gap: var(--sui-sp-2);
193+
align-items: stretch;
205194
}
206-
.advanced-toggle:hover { background: var(--sui-bg-hover); color: var(--sui-fg); }
195+
.path-input { flex: 1; }
207196
.nested-form-item { margin-top: var(--sui-sp-3); }
208197
</style>

packages/admin/src/views/chat/ChannelsView.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,7 @@ async function refresh() {
969969
:memory-options="memoryOptions"
970970
:wiki-options="wikiOptions"
971971
:model-options="modelOptions"
972+
@browse-path="pathPicker?.open(sessionForm.overrides.workPath || '')"
972973
/>
973974
</div>
974975
<div class="drawer-footer">
@@ -1008,7 +1009,7 @@ async function refresh() {
10081009
ref="pathPicker"
10091010
:transport="pickerTransport"
10101011
:labels="pickerLabels"
1011-
@confirm="p => { if (editingSession) sessionForm.workPath = p; else form.workPath = p }"
1012+
@confirm="p => { if (editingSession) sessionForm.overrides.workPath = p; else form.workPath = p }"
10121013
@error="msg => show(msg, 'error')"
10131014
/>
10141015
</div>

packages/admin/src/views/chat/SessionProfilesView.vue

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { store } from '@/shared/store'
66
import { useToast, useConfirm, SButton, SInput, SFormItem, SPageToolbar, SPageContent, STable } from 'sbot-ui'
77
import type { STableColumn } from 'sbot-ui'
88
import { ApprovalTimeoutValue } from 'sbot.commons'
9+
import { PathPickerModal, WebSocketTransport } from '@sbot/chat-ui'
910
import SessionConfigOverridesEditor, { type SessionOverrides } from '@/components/SessionConfigOverridesEditor.vue'
1011
1112
interface ProfileRow {
@@ -39,6 +40,20 @@ const { t } = useI18n()
3940
const { show } = useToast()
4041
const { confirm } = useConfirm()
4142
43+
const pickerTransport = new WebSocketTransport()
44+
const pickerLabels = computed(() => ({
45+
selectDirTitle: t('directory.select_dir_title'),
46+
myComputer: t('directory.my_computer'),
47+
upDir: t('directory.up_dir'),
48+
newFolder: t('directory.new_folder'),
49+
newFolderPlaceholder: t('directory.new_folder_placeholder'),
50+
selectThis: t('directory.select_this'),
51+
noSubdirs: t('directory.no_subdirs'),
52+
loading: t('common.loading'),
53+
cancel: t('common.cancel'),
54+
}))
55+
const pathPicker = ref<InstanceType<typeof PathPickerModal>>()
56+
4257
const profiles = ref<ProfileRow[]>([])
4358
4459
const agentOptions = computed(() => Object.entries(store.settings.agents || {}).map(([id, a]) => ({ id, label: (a as any).name || id, type: (a as any).type || '' })))
@@ -247,6 +262,7 @@ async function remove(p: ProfileRow) {
247262
:memory-options="memoryOptions"
248263
:wiki-options="wikiOptions"
249264
:model-options="modelOptions"
265+
@browse-path="pathPicker?.open(form.overrides.workPath || '')"
250266
/>
251267
</div>
252268
<div class="drawer-footer">
@@ -255,6 +271,14 @@ async function remove(p: ProfileRow) {
255271
</div>
256272
</div>
257273
</Transition>
274+
275+
<PathPickerModal
276+
ref="pathPicker"
277+
:transport="pickerTransport"
278+
:labels="pickerLabels"
279+
@confirm="p => { form.overrides.workPath = p }"
280+
@error="msg => show(msg, 'error')"
281+
/>
258282
</div>
259283
</template>
260284

packages/chat-ui/src/components/ChatView.vue

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import ConfigToolbar from './ConfigToolbar.vue'
1616
import StatusBar from './StatusBar.vue'
1717
import ChatArea from './ChatArea.vue'
1818
import PathPickerModal from './PathPickerModal.vue'
19-
import Explorer from './Explorer.vue'
19+
import RightPanel from './RightPanel.vue'
2020
import { useConfirm } from 'sbot-ui'
2121
2222
const props = withDefaults(defineProps<{
@@ -61,7 +61,6 @@ const isCompact = useCompactProvider(rootEl)
6161
const sidebarOpen = ref(false)
6262
const settingsOpen = ref(false)
6363
const explorerOpen = ref(false)
64-
const explorerTouched = ref(false)
6564
const explorerWidth = ref(420)
6665
const explorerResizing = ref(false)
6766
const sessionBarWidth = ref(180)
@@ -122,10 +121,6 @@ const explorerPaneStyle = computed(() =>
122121
: { width: `${explorerWidth.value}px` },
123122
)
124123
125-
const shouldAutoOpenExplorer = computed(() =>
126-
Boolean(activeSession.value?.workPath) && !isCompact.value && !props.alwaysCompact,
127-
)
128-
129124
// ── Event handler ──
130125
131126
function handleEvent(evt: ChatEvent) {
@@ -281,7 +276,6 @@ function toggleSettings() {
281276
}
282277
283278
function toggleExplorer() {
284-
explorerTouched.value = true
285279
explorerOpen.value = !explorerOpen.value
286280
if (explorerOpen.value && (isCompact.value || props.alwaysCompact)) {
287281
sidebarOpen.value = false
@@ -343,7 +337,6 @@ function stopSessionBarResize() {
343337
}
344338
345339
watch(activeSessionId, async (id) => {
346-
explorerTouched.value = false
347340
const gen = ++loadGeneration
348341
resetStreamState()
349342
messages.value = []
@@ -353,10 +346,6 @@ watch(activeSessionId, async (id) => {
353346
await Promise.all([loadHistory(gen), loadUsage(gen), restoreSessionStatus(gen)])
354347
})
355348
356-
watch(shouldAutoOpenExplorer, (value) => {
357-
if (!explorerTouched.value) explorerOpen.value = value
358-
}, { immediate: true })
359-
360349
async function loadHistory(gen = ++loadGeneration) {
361350
const id = activeSessionId.value
362351
if (!id) return
@@ -806,7 +795,7 @@ onBeforeUnmount(() => {
806795
<span class="chatui-explorer-panel-title">{{ L.explorerToggle }}</span>
807796
<button class="chatui-explorer-panel-close" :title="L.close" @click="explorerOpen = false">×</button>
808797
</div>
809-
<Explorer :transport="transport" :root="activeSession?.workPath" :labels="labels" editable />
798+
<RightPanel :transport="transport" :root="activeSession?.workPath" :labels="labels" editable />
810799
</div>
811800
</div>
812801
</div>

0 commit comments

Comments
 (0)