import * as api from '@/api/index.js'
/**
* Session content that lives beside the transcript: published artifacts, the
* session's file tree, and message feedback.
*/
export function createContentActions(state) {
const { currentId, artifacts, files, messages } = state
// Last session each side panel asked about. A response for a session the
// user already left must not repaint the panel (artifacts/files of one
// session showing under another) — a plain currentId check cannot be used
// here because loadSession fills them before it sets currentId.
let artifactsReq = null
let filesReq = null
let feedbackReq = null
async function fetchArtifacts(id = currentId.value) {
if (!id) {
artifacts.value = []
return
}
artifactsReq = id
try {
const { content = [] } = await api.getSessionContent(id)
if (artifactsReq === id) artifacts.value = content
} catch {
if (artifactsReq === id) artifacts.value = []
}
}
async function fetchFiles(id = currentId.value) {
if (!id) {
files.value = []
return
}
filesReq = id
try {
const { files: list = [] } = await api.listSessionFiles(id)
if (filesReq === id) files.value = list
} catch {
if (filesReq === id) files.value = []
}
}
function upsertArtifact(item) {
if (!item?.filename) return
const key = item.id || item.filename
const idx = artifacts.value.findIndex(a => (a.id || a.filename) === key)
const next = { ...item }
if (idx === -1) artifacts.value.unshift(next)
else artifacts.value.splice(idx, 1, { ...artifacts.value[idx], ...next })
}
// Pull stored ratings for this session and stamp them onto built messages.
// Each assistant block has id `h_<index>` where index is its first raw position;
// that index is also the feedback key (session_id, message_index).
async function mergeFeedback(id) {
// Ratings are stamped onto the array that belongs to this load. If the
// transcript was replaced (another session opened) while the request was
// in flight, the ids in the response no longer mean what they did.
const target = messages.value
feedbackReq = id
try {
const { feedback = [] } = await api.getFeedback(id)
if (feedbackReq !== id || messages.value !== target) return
if (!feedback.length) return
const map = new Map(feedback.map(f => [f.message_index, f.rating]))
for (const m of messages.value) {
if (m.role !== 'assistant') continue
const idx = m.id?.startsWith?.('h_') ? Number(m.id.slice(2)) : NaN
if (Number.isInteger(idx) && map.has(idx)) {
m.rating = map.get(idx)
}
}
} catch {
// Feedback is non-critical — ignore failures so chat still works.
}
}
// Toggle / set feedback on an assistant block. Optimistic — local state
// updates first, server call follows; rolls back on failure.
async function rateMessage(msg, rating) {
if (!currentId.value || !msg) return
const idx = msg.id?.startsWith?.('h_') ? Number(msg.id.slice(2)) : NaN
if (!Number.isInteger(idx)) return
// Toggle off if user clicks the active rating again
const next = msg.rating === rating ? 0 : rating
const prev = msg.rating ?? 0
msg.rating = next
try {
await api.setFeedback(currentId.value, idx, next)
} catch (err) {
msg.rating = prev
throw err
}
}
return { fetchArtifacts, fetchFiles, upsertArtifact, mergeFeedback, rateMessage }
}