feat(cis): add CCTranscriptionPanel Live tab to sidebar (Phase 1)
- Add CCTranscriptionPanel component with Live tab - Add Zustand transcriptionStore for session state management - Wire panel into BasePanel sidebar system - Fix merged switch cases in getIconForPanel, getDescriptionForPanel, renderCurrentPanel - Add VITE_WHISPERLIVE_URL to .env
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface TranscriptionSegment {
|
||||
text: string;
|
||||
isFinal: boolean;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
interface TranscriptionState {
|
||||
isRecording: boolean;
|
||||
isConnecting: boolean;
|
||||
completedSegments: TranscriptionSegment[];
|
||||
currentSegment: TranscriptionSegment | null;
|
||||
wordCount: number;
|
||||
elapsedSeconds: number;
|
||||
|
||||
startSession: () => void;
|
||||
stopSession: () => void;
|
||||
saveSegment: (text: string, isFinal: boolean, metadata: { start: number; end: number }) => void;
|
||||
resetSession: () => void;
|
||||
tickElapsed: () => void;
|
||||
}
|
||||
|
||||
export const useTranscriptionStore = create<TranscriptionState>((set, get) => ({
|
||||
isRecording: false,
|
||||
isConnecting: false,
|
||||
completedSegments: [],
|
||||
currentSegment: null,
|
||||
wordCount: 0,
|
||||
elapsedSeconds: 0,
|
||||
|
||||
startSession: () => {
|
||||
set({ isRecording: true, isConnecting: false, elapsedSeconds: 0 });
|
||||
},
|
||||
|
||||
stopSession: () => {
|
||||
set({ isRecording: false, isConnecting: false });
|
||||
},
|
||||
|
||||
saveSegment: (text: string, isFinal: boolean, metadata: { start: number; end: number }) => {
|
||||
const { completedSegments, currentSegment } = get();
|
||||
|
||||
if (isFinal) {
|
||||
// Final segment — move current to completed, clear current
|
||||
const newCompleted = [...completedSegments];
|
||||
if (currentSegment && currentSegment.text.trim()) {
|
||||
newCompleted.push({ ...currentSegment, isFinal: true });
|
||||
}
|
||||
const newWordCount = newCompleted.reduce(
|
||||
(sum, seg) => sum + seg.text.trim().split(/\s+/).filter(Boolean).length,
|
||||
0
|
||||
);
|
||||
set({ completedSegments: newCompleted, currentSegment: null, wordCount: newWordCount });
|
||||
} else {
|
||||
// In-progress segment
|
||||
set({ currentSegment: { text, isFinal: false, ...metadata } });
|
||||
}
|
||||
},
|
||||
|
||||
resetSession: () => {
|
||||
set({ isRecording: false, isConnecting: false, completedSegments: [], currentSegment: null, wordCount: 0, elapsedSeconds: 0 });
|
||||
},
|
||||
|
||||
tickElapsed: () => {
|
||||
set((state) => ({ elapsedSeconds: state.elapsedSeconds + 1 }));
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user