@@ -3,7 +3,7 @@ import { ref, computed, onMounted } from 'vue'
33import { useI18n } from ' vue-i18n'
44import { apiFetch } from ' @/shared/api'
55import { store } from ' @/shared/store'
6- import { useToast , useConfirm , SButton , SInput , SFormItem , SPageToolbar , SPageContent , STable } from ' sbot-ui'
6+ import { useToast , useConfirm , SButton , SInput , SFormItem , SPageToolbar , SPageContent , STable , SModal } from ' sbot-ui'
77import type { STableColumn } from ' sbot-ui'
88import { ApprovalTimeoutValue } from ' sbot.commons'
99import { PathPickerModal , WebSocketTransport } from ' @sbot/chat-ui'
@@ -56,6 +56,39 @@ const pathPicker = ref<InstanceType<typeof PathPickerModal>>()
5656
5757const profiles = ref <ProfileRow []>([])
5858
59+ interface SessionLite {
60+ id: number
61+ channelId: string
62+ sessionId: string
63+ sessionName: string
64+ autoSessionName: string
65+ }
66+ const sessionsModalVisible = ref (false )
67+ const sessionsModalProfile = ref <ProfileRow | null >(null )
68+ const sessionsModalRows = ref <SessionLite []>([])
69+ const sessionsModalLoading = ref (false )
70+
71+ const channelLabel = (channelId : string ): string => {
72+ const c = (store .settings .channels as Record <string , any > | undefined )?.[channelId ]
73+ return c ?.name || channelId
74+ }
75+
76+ async function openSessions(p : ProfileRow ) {
77+ if (! (p .sessionCount ?? 0 )) return
78+ sessionsModalProfile .value = p
79+ sessionsModalRows .value = []
80+ sessionsModalVisible .value = true
81+ sessionsModalLoading .value = true
82+ try {
83+ const res = await apiFetch (` /api/session-profiles/${p .id } ` )
84+ sessionsModalRows .value = ((res .data as any )?.sessions || []) as SessionLite []
85+ } catch (e : any ) {
86+ show (e .message , ' error' )
87+ } finally {
88+ sessionsModalLoading .value = false
89+ }
90+ }
91+
5992const agentOptions = computed (() => Object .entries (store .settings .agents || {}).map (([id , a ]) => ({ id , label: (a as any ).name || id , type: (a as any ).type || ' ' })))
6093const saverOptions = computed (() => Object .entries (store .settings .savers || {}).map (([id , s ]) => ({ id , label: (s as any ).name || id })))
6194const memoryOptions = computed (() => Object .entries (store .settings .memories || {}).map (([id , m ]: [string , any ]) => ({ id , label: m .name || id })))
@@ -231,7 +264,9 @@ async function remove(p: ProfileRow) {
231264 <span v-else style =" color : var (--sui-fg-disabled )" >—</span >
232265 </template >
233266 <template #sessionCount =" { row } " >
234- <span v-if =" row.sessionCount" >{{ t('session_profiles.used_by_n', { n: row.sessionCount }) }}</span >
267+ <a v-if =" row.sessionCount" href =" #" class =" session-count-link" @click.prevent =" openSessions(row)" >
268+ {{ t('session_profiles.used_by_n', { n: row.sessionCount }) }}
269+ </a >
235270 <span v-else style =" color : var (--sui-fg-disabled )" >{{ t('session_profiles.used_by_none') }}</span >
236271 </template >
237272 <template #ops =" { row } " >
@@ -279,6 +314,22 @@ async function remove(p: ProfileRow) {
279314 @confirm =" p => { form .overrides .workPath = p }"
280315 @error =" msg => show (msg , ' error' )"
281316 />
317+
318+ <SModal v-model :visible =" sessionsModalVisible " width="md">
319+ <template #header >
320+ <h3 class =" s-modal-title" >
321+ {{ t('session_profiles.sessions_modal_title', { name: sessionsModalProfile?.name || '' }) }}
322+ </h3 >
323+ </template >
324+ <div v-if =" sessionsModalLoading" class =" modal-loading" >{{ t('common.loading') }}</div >
325+ <div v-else-if =" sessionsModalRows.length === 0" class =" modal-empty" >{{ t('session_profiles.used_by_none') }}</div >
326+ <ul v-else class =" sess-list" >
327+ <li v-for =" s in sessionsModalRows" :key =" s.id" class =" sess-row" >
328+ <span class =" sess-channel" >{{ channelLabel(s.channelId) }}</span >
329+ <span class =" sess-name" >{{ s.sessionName || s.autoSessionName || s.sessionId }}</span >
330+ </li >
331+ </ul >
332+ </SModal >
282333 </div >
283334</template >
284335
@@ -313,4 +364,18 @@ async function remove(p: ProfileRow) {
313364.drawer-fade-enter-from , .drawer-fade-leave-to { opacity : 0 ; }
314365.drawer-slide-enter-active , .drawer-slide-leave-active { transition : transform 0.25s ease ; }
315366.drawer-slide-enter-from , .drawer-slide-leave-to { transform : translateX (100% ); }
367+
368+ .session-count-link { color : var (--sui-fg-link , var (--sui-accent )); text-decoration : none ; cursor : pointer ; }
369+ .session-count-link :hover { text-decoration : underline ; }
370+
371+ .modal-loading , .modal-empty { padding : var (--sui-sp-6 ); text-align : center ; color : var (--sui-fg-muted ); }
372+ .sess-list { list-style : none ; margin : 0 ; padding : 0 ; display : flex ; flex-direction : column ; gap : var (--sui-sp-2 ); }
373+ .sess-row {
374+ display : flex ; align-items : center ; gap : var (--sui-sp-3 );
375+ padding : var (--sui-sp-3 ) var (--sui-sp-4 );
376+ border : 1px solid var (--sui-border );
377+ border-radius : var (--sui-radius );
378+ }
379+ .sess-channel { color : var (--sui-fg-muted ); font-size : var (--sui-fs-sm ); min-width : 120px ; }
380+ .sess-name { flex : 1 ; }
316381 </style >
0 commit comments