feat: integrate MarkBase Search Session API into Studio
- Created useSearchSession composable for session management - Added 7 API endpoints in api/index.ts - Updated SearchView.vue to use sessions instead of history - Session sidebar with create, rename, delete functionality - Auto-save messages to current session after each search - Auto-create default session on first load - Updated CSS for session active state and new session button
This commit is contained in:
219
src/composables/useSearchSession.ts
Normal file
219
src/composables/useSearchSession.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import { ref } from 'vue'
|
||||
import { getApiBase } from '@/api/config'
|
||||
|
||||
export interface SearchSession {
|
||||
id: string
|
||||
title: string
|
||||
mode: 'keyword' | 'visual' | 'people' | 'agent'
|
||||
message_count: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface SearchMessage {
|
||||
id: string
|
||||
session_id: string
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
results: string
|
||||
mode: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export function useSearchSession() {
|
||||
const sessions = ref<SearchSession[]>([])
|
||||
const currentSession = ref<SearchSession | null>(null)
|
||||
const messages = ref<SearchMessage[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
function getAuthHeaders(): Record<string, string> {
|
||||
const stored = localStorage.getItem('mb_admin_token')
|
||||
if (stored) {
|
||||
try {
|
||||
const data = JSON.parse(stored)
|
||||
if (new Date(data.expires_at) > new Date()) {
|
||||
return { 'Authorization': `Bearer ${data.token}` }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
async function loadSessions() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const base = getApiBase()
|
||||
const resp = await fetch(`${base}/api/v2/search-sessions`, {
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
const data = await resp.json()
|
||||
sessions.value = data.sessions || []
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to load sessions'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createSession(title?: string, mode?: string) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const base = getApiBase()
|
||||
const resp = await fetch(`${base}/api/v2/search-sessions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ title: title || '未命名搜尋', mode: mode || 'keyword' })
|
||||
})
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
const data = await resp.json()
|
||||
await loadSessions()
|
||||
return data.session
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to create session'
|
||||
return null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function renameSession(id: string, title: string) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const base = getApiBase()
|
||||
const resp = await fetch(`${base}/api/v2/search-sessions/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ title })
|
||||
})
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
await loadSessions()
|
||||
if (currentSession.value?.id === id) {
|
||||
currentSession.value.title = title
|
||||
}
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to rename session'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSession(id: string) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const base = getApiBase()
|
||||
const resp = await fetch(`${base}/api/v2/search-sessions/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
if (currentSession.value?.id === id) {
|
||||
currentSession.value = null
|
||||
messages.value = []
|
||||
}
|
||||
await loadSessions()
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to delete session'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMessages(sessionId: string) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const base = getApiBase()
|
||||
const resp = await fetch(`${base}/api/v2/search-sessions/${sessionId}/messages`, {
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
const data = await resp.json()
|
||||
messages.value = data.messages || []
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to load messages'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveMessage(sessionId: string, message: { role: string; content: string; mode?: string; results?: any }) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const base = getApiBase()
|
||||
const resp = await fetch(`${base}/api/v2/search-sessions/${sessionId}/messages`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
mode: message.mode || 'keyword',
|
||||
results: message.results ? JSON.stringify(message.results) : ''
|
||||
})
|
||||
})
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
const data = await resp.json()
|
||||
await loadMessages(sessionId)
|
||||
await loadSessions()
|
||||
return data.message
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to save message'
|
||||
return null
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateMessage(sessionId: string, msgId: string, update: { role?: string; content?: string; results?: any }) {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const base = getApiBase()
|
||||
const resp = await fetch(`${base}/api/v2/search-sessions/${sessionId}/messages/${msgId}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({
|
||||
...update,
|
||||
results: update.results ? JSON.stringify(update.results) : undefined
|
||||
})
|
||||
})
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
await loadMessages(sessionId)
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to update message'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function setCurrentSession(session: SearchSession | null) {
|
||||
currentSession.value = session
|
||||
if (session) {
|
||||
loadMessages(session.id)
|
||||
} else {
|
||||
messages.value = []
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sessions,
|
||||
currentSession,
|
||||
messages,
|
||||
loading,
|
||||
error,
|
||||
loadSessions,
|
||||
createSession,
|
||||
renameSession,
|
||||
deleteSession,
|
||||
loadMessages,
|
||||
saveMessage,
|
||||
updateMessage,
|
||||
setCurrentSession
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user