File size: 4,204 Bytes
a45ece3
 
 
 
 
 
 
 
 
 
 
 
98feb0f
a45ece3
 
 
 
 
 
 
 
 
 
 
98feb0f
a45ece3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98feb0f
a45ece3
 
98feb0f
a45ece3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { nanoid } from '@/store/nanoid'
import type { CallReport, InputKind, RunRecord, VerdictLabel } from '@/lib/types'
import { processAudio, processTranscript } from '@/lib/api'

export type StepKey = 'ASR' | 'Claims' | 'Retrieval' | 'Verification' | 'Summary'
export type StepStatus = 'idle' | 'running' | 'done' | 'error'

export type UIState = {
  tab: 0 | 1 | 2
  filter: 'all' | VerdictLabel
  speakerFilter: 'all' | string
  evidenceOpen: boolean
  selectedClaimId?: string
}

type Store = {
  runs: RunRecord[]
  currentRunId?: string
  steps: Record<StepKey, StepStatus>
  ui: UIState
  setTab: (tab: 0 | 1 | 2) => void
  setFilter: (f: 'all' | VerdictLabel) => void
  setSpeakerFilter: (f: 'all' | string) => void
  openEvidence: (claimId: string) => void
  closeEvidence: () => void
  startRun: (kind: InputKind, payload: { file?: File; text?: string }) => Promise<void>
  loadSample: () => Promise<void>
  loadFromStorage: () => void
  selectRun: (id: string) => void
}

const initialSteps: Record<StepKey, StepStatus> = {
  ASR: 'idle',
  Claims: 'idle',
  Retrieval: 'idle',
  Verification: 'idle',
  Summary: 'idle',
}

export const useAppStore = create<Store>()(
  persist(
    (set, get) => ({
      runs: [],
      currentRunId: undefined,
      steps: initialSteps,
      ui: { tab: 0, filter: 'all', speakerFilter: 'all', evidenceOpen: false },
      setTab: (tab) => set((s) => ({ ui: { ...s.ui, tab } })),
      setFilter: (filter) => set((s) => ({ ui: { ...s.ui, filter } })),
      setSpeakerFilter: (speakerFilter) => set((s) => ({ ui: { ...s.ui, speakerFilter } })),
      openEvidence: (selectedClaimId) => set((s) => ({ ui: { ...s.ui, evidenceOpen: true, selectedClaimId } })),
      closeEvidence: () => set((s) => ({ ui: { ...s.ui, evidenceOpen: false, selectedClaimId: undefined } })),

      async startRun(kind, payload) {
        const id = nanoid()
        const startedAt = Date.now()
        const newRun: RunRecord = { id, startedAt, inputKind: kind }
        set((s) => ({ runs: [newRun, ...s.runs].slice(0, 5), currentRunId: id }))

        // optimistic step progression
        const audio = kind === 'audio'
        set({ steps: {
          ASR: audio ? 'running' : 'done',
          Claims: 'idle',
          Retrieval: 'idle',
          Verification: 'idle',
          Summary: 'idle',
        } })

        try {
          let report: CallReport
          if (kind === 'audio' && payload.file) {
            report = await processAudio(payload.file)
          } else if (kind === 'transcript' && payload.text) {
            report = await processTranscript(payload.text)
          } else {
            throw new Error('Missing input')
          }

          // set done
          set({ steps: { ASR: audio ? 'done' : 'done', Claims: 'done', Retrieval: 'done', Verification: 'done', Summary: 'done' } })
          set((s) => ({ runs: s.runs.map((r) => (r.id === id ? { ...r, report } : r)) }))
        } catch (err: any) {
          set({ steps: { ...initialSteps, ASR: audio ? 'error' : 'done', Claims: 'error' } })
          const message = err?.response?.data?.detail || err?.message || 'Failed to process request'
          set((s) => ({ runs: s.runs.map((r) => (r.id === id ? { ...r, error: message } : r)) }))
        }
      },

      async loadSample() {
        const id = nanoid()
        const startedAt = Date.now()
        const newRun: RunRecord = { id, startedAt, inputKind: 'sample' }
        set((s) => ({ runs: [newRun, ...s.runs].slice(0, 5), currentRunId: id }))
        set({ steps: { ASR: 'done', Claims: 'done', Retrieval: 'done', Verification: 'done', Summary: 'done' } })
        const data = await import('@/mocks/sampleReport.json')
        const report = data.default
        set((s) => ({ runs: s.runs.map((r) => (r.id === id ? { ...r, report } : r)) }))
      },

      loadFromStorage() {
        // noop: handled by persist middleware rehydration
      },

      selectRun(id) {
        set({ currentRunId: id })
      },
    }),
    {
      name: 'cc_runs',
      partialize: (s) => ({ runs: s.runs, currentRunId: s.currentRunId }),
    }
  )
)