Newer
Older
navi-1 / webclient / src / stores / chat / content.js
@Eugene Sukhodolskiy Eugene Sukhodolskiy 23 hours ago 2 KB webclient: split the chat store into area modules
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

  async function fetchArtifacts(id = currentId.value) {
    if (!id) {
      artifacts.value = []
      return
    }
    try {
      const { content = [] } = await api.getSessionContent(id)
      artifacts.value = content
    } catch {
      artifacts.value = []
    }
  }

  async function fetchFiles(id = currentId.value) {
    if (!id) {
      files.value = []
      return
    }
    try {
      const { files: list = [] } = await api.listSessionFiles(id)
      files.value = list
    } catch {
      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) {
    try {
      const { feedback = [] } = await api.getFeedback(id)
      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 }
}