@@ -3,7 +3,7 @@ import { computed, ref } from 'vue'
33import { useI18n } from ' vue-i18n'
44import { apiFetch } from ' @/shared/api'
55import { store } from ' @/shared/store'
6- import { useToast , SButton , SModal , SBadge } from ' sbot-ui'
6+ import { useToast , SButton , SModal , SBadge , SSelect } from ' sbot-ui'
77
88interface MemorySummary {
99 slug: string
@@ -14,8 +14,11 @@ interface MemorySummary {
1414 updatedAt: number
1515 lastReadAt: number | null
1616 readCount: number
17+ scope: ' global' | ' workspace'
1718}
1819
20+ interface WorkspaceScope { key: string ; path: string }
21+
1922interface MemoryJob {
2023 id: number
2124 type: string
@@ -32,6 +35,8 @@ const { show } = useToast()
3235const visible = ref (false )
3336const memoryId = ref (' ' )
3437const labelOverride = ref (' ' )
38+ const workspaceScopes = ref <WorkspaceScope []>([])
39+ const selectedWorkPath = ref (' ' )
3540
3641const tab = ref <' memories' | ' jobs' >(' memories' )
3742const loading = ref (false )
@@ -44,10 +49,14 @@ const retryingJobId = ref<number | null>(null)
4449
4550const rows = ref <MemorySummary []>([])
4651const jobs = ref <MemoryJob []>([])
47- const selectedSlug = ref (' ' )
52+ const selectedKey = ref (' ' )
4853const selectedBody = ref (' ' )
4954
50- const selected = computed (() => rows .value .find (m => m .slug === selectedSlug .value ) || null )
55+ const rowKey = (m : Pick <MemorySummary , ' scope' | ' slug' >) => ` ${m .scope }:${m .slug } `
56+ const selected = computed (() => rows .value .find (m => rowKey (m ) === selectedKey .value ) || null )
57+ const viewQuery = computed (() => selectedWorkPath .value
58+ ? ` viewScope=workspace&workPath=${encodeURIComponent (selectedWorkPath .value )} `
59+ : ' viewScope=global' )
5160
5261const title = computed (() => {
5362 const profiles: any = store .settings .memoryProfiles || {}
@@ -60,8 +69,22 @@ async function openByMemoryId(id: string | null | undefined, label?: string) {
6069 labelOverride .value = label || ' '
6170 tab .value = ' memories'
6271 visible .value = true
63- if (memoryId .value ) await refresh ()
64- else { rows .value = []; jobs .value = []; selectedSlug .value = ' ' ; selectedBody .value = ' ' }
72+ selectedWorkPath .value = ' '
73+ if (memoryId .value ) {
74+ await loadScopes ()
75+ await refresh ()
76+ } else { rows .value = []; jobs .value = []; selectedKey .value = ' ' ; selectedBody .value = ' ' }
77+ }
78+
79+ async function loadScopes() {
80+ const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/scopes ` )
81+ workspaceScopes .value = (res .data ?.workspaces || []) as WorkspaceScope []
82+ }
83+
84+ async function changeScope() {
85+ selectedKey .value = ' '
86+ selectedBody .value = ' '
87+ await refresh ()
6588}
6689
6790async function refresh() {
@@ -73,12 +96,12 @@ async function loadMemories() {
7396 if (! memoryId .value ) return
7497 loading .value = true
7598 try {
76- const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/list ` )
99+ const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/list?${ viewQuery . value } ` )
77100 const list = (res .data ?.memories || []) as MemorySummary []
78101 rows .value = list
79- const nextSlug = list .find (r => r . slug === selectedSlug .value )?. slug || list [0 ]?. slug || ' '
80- selectedSlug .value = nextSlug
81- if (nextSlug ) await loadBody (nextSlug )
102+ const next = list .find (r => rowKey ( r ) === selectedKey .value ) || list [0 ]
103+ selectedKey .value = next ? rowKey ( next ) : ' '
104+ if (next ) await loadBody (next )
82105 else selectedBody .value = ' '
83106 } catch (e : any ) {
84107 show (e .message , ' error' )
@@ -91,7 +114,8 @@ async function loadJobs() {
91114 if (! memoryId .value ) return
92115 jobsLoading .value = true
93116 try {
94- const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/jobs?limit=50 ` )
117+ const query = [viewQuery .value , ' limit=50' ].join (' &' )
118+ const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/jobs?${query } ` )
95119 jobs .value = (res .data ?.jobs || []) as MemoryJob []
96120 } catch (e : any ) {
97121 show (e .message , ' error' )
@@ -100,17 +124,19 @@ async function loadJobs() {
100124 }
101125}
102126
103- async function selectMemory(slug : string ) {
104- if (selectedSlug .value === slug && selectedBody .value ) return
105- selectedSlug .value = slug
106- await loadBody (slug )
127+ async function selectMemory(memory : MemorySummary ) {
128+ const key = rowKey (memory )
129+ if (selectedKey .value === key && selectedBody .value ) return
130+ selectedKey .value = key
131+ await loadBody (memory )
107132}
108133
109- async function loadBody(slug : string ) {
110- if (! memoryId .value || ! slug ) return
134+ async function loadBody(memory : MemorySummary ) {
135+ if (! memoryId .value || ! memory . slug ) return
111136 bodyLoading .value = true
112137 try {
113- const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/entries/${encodeURIComponent (slug )} ` )
138+ const query = [viewQuery .value , ` entryScope=${memory .scope } ` ].join (' &' )
139+ const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/entries/${encodeURIComponent (memory .slug )}?${query } ` )
114140 selectedBody .value = res .data ?.row ?.body || ' '
115141 } catch (e : any ) {
116142 selectedBody .value = ' '
@@ -124,7 +150,7 @@ async function runConsolidate() {
124150 if (! memoryId .value || consolidating .value ) return
125151 consolidating .value = true
126152 try {
127- const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/consolidate/run ` , ' POST' , {})
153+ const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/consolidate/run?${ viewQuery . value } ` , ' POST' , {})
128154 show (t (' memory_profiles.consolidate_queued' , { id: res .data ?.jobId ?? ' -' }))
129155 await refresh ()
130156 } catch (e : any ) {
@@ -138,7 +164,7 @@ async function runReconcile() {
138164 if (! memoryId .value || reconciling .value ) return
139165 reconciling .value = true
140166 try {
141- const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/reconcile/run ` , ' POST' , {})
167+ const res = await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/reconcile/run?${ viewQuery . value } ` , ' POST' , {})
142168 show (t (' memory_profiles.reconcile_queued' , { id: res .data ?.jobId ?? ' -' }))
143169 await refresh ()
144170 } catch (e : any ) {
@@ -166,15 +192,16 @@ async function retryExtractJob(job: MemoryJob) {
166192 }
167193}
168194
169- async function deleteMemory(slug : string ) {
170- if (! memoryId .value || ! slug || deleting .value ) return
171- if (! window .confirm (t (' memory_profiles.confirm_delete_memory' , { slug }))) return
195+ async function deleteMemory(memory : MemorySummary ) {
196+ if (! memoryId .value || ! memory . slug || deleting .value ) return
197+ if (! window .confirm (t (' memory_profiles.confirm_delete_memory' , { slug: memory . slug }))) return
172198 deleting .value = true
173199 try {
174- await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/entries/${encodeURIComponent (slug )} ` , ' DELETE' )
200+ const query = [viewQuery .value , ` entryScope=${memory .scope } ` ].join (' &' )
201+ await apiFetch (` /api/memories/${encodeURIComponent (memoryId .value )}/entries/${encodeURIComponent (memory .slug )}?${query } ` , ' DELETE' )
175202 show (t (' memory_profiles.delete_memory_done' ))
176- if (selectedSlug .value === slug ) {
177- selectedSlug .value = ' '
203+ if (selectedKey .value === rowKey ( memory ) ) {
204+ selectedKey .value = ' '
178205 selectedBody .value = ' '
179206 }
180207 await loadMemories ()
@@ -231,8 +258,17 @@ defineExpose({ openByMemoryId })
231258 <SButton :type =" tab === ' jobs' ? ' primary' : ' outline' " size="sm" @click =" tab = ' jobs' " >
232259 {{ t('memory_profiles.viewer_jobs') }}
233260 </SButton >
261+ <SSelect v-model =" selectedWorkPath " size="sm" @change =" changeScope " >
262+ <option value =" " >{{ t('memory_profiles.scope_global') }}</option >
263+ <option v-for =" scope in workspaceScopes" :key =" scope.key" :value =" scope.path" >
264+ {{ t('memory_profiles.scope_workspace_context', { path: scope.path }) }}
265+ </option >
266+ </SSelect >
234267 </div >
235268 <div class =" memory-actions" >
269+ <SBadge variant="info" size="sm">
270+ {{ t(selectedWorkPath ? 'memory_profiles.operation_scope_workspace' : 'memory_profiles.operation_scope_global') }}
271+ </SBadge >
236272 <SButton type="outline" size="sm" :loading =" loading || jobsLoading " @click =" refresh " >{{ t('common.refresh') }}</SButton >
237273 <SButton type="outline" size="sm" :loading =" reconciling " @click =" runReconcile " >{{ t('memory_profiles.run_reconcile') }}</SButton >
238274 <SButton type="outline" size="sm" :loading =" consolidating " @click =" runConsolidate " >{{ t('memory_profiles.run_consolidate') }}</SButton >
@@ -246,13 +282,16 @@ defineExpose({ openByMemoryId })
246282 <button
247283 v-for =" m in rows"
248284 v-else
249- :key =" m.slug "
285+ :key =" rowKey(m) "
250286 class =" memory-row"
251- :class =" { active: m.slug === selectedSlug }"
252- @click =" selectMemory(m.slug )"
287+ :class =" { active: rowKey(m) === selectedKey }"
288+ @click =" selectMemory(m)"
253289 >
254290 <div class =" memory-row-head" >
255- <SBadge :variant =" kindVariant (m .kind )" size="xs">{{ m.kind }}</SBadge >
291+ <div class =" memory-row-badges" >
292+ <SBadge variant="neutral" size="xs">{{ m.scope }}</SBadge >
293+ <SBadge :variant =" kindVariant (m .kind )" size="xs">{{ m.kind }}</SBadge >
294+ </div >
256295 <span class =" memory-row-slug" >{{ m.slug }}</span >
257296 </div >
258297 <div class =" memory-row-title" >{{ m.title }}</div >
@@ -270,10 +309,11 @@ defineExpose({ openByMemoryId })
270309 <div class =" memory-detail-slug" >{{ selected.slug }}</div >
271310 </div >
272311 <div class =" memory-detail-badges" >
312+ <SBadge variant="neutral" size="sm">{{ selected.scope }}</SBadge >
273313 <SBadge :variant =" kindVariant (selected .kind )" size="sm">{{ selected.kind }}</SBadge >
274314 <SBadge variant="neutral" size="sm">{{ t('memory_profiles.evidence') }} {{ selected.evidenceCount }}</SBadge >
275315 <SBadge variant="neutral" size="sm">{{ t('memory_profiles.read_count') }} {{ selected.readCount }}</SBadge >
276- <SButton type="danger" size="sm" :loading =" deleting " @click =" deleteMemory (selected . slug )" >
316+ <SButton type="danger" size="sm" :loading =" deleting " @click =" deleteMemory (selected )" >
277317 {{ t('memory_profiles.delete_memory') }}
278318 </SButton >
279319 </div >
@@ -379,6 +419,7 @@ defineExpose({ openByMemoryId })
379419}
380420
381421.memory-row-head ,
422+ .memory-row-badges ,
382423.memory-row-meta ,
383424.memory-detail-badges ,
384425.memory-job-head ,
0 commit comments