chat history

This commit is contained in:
2026-03-01 13:25:25 +10:00
parent ff6e946529
commit 70f8c00e5e
4 changed files with 268 additions and 55 deletions

View File

@@ -63,11 +63,6 @@
line-height: 1.4; line-height: 1.4;
} }
/* Push Clear button to the far right inside the tab bar */
.chat-tabs .chat-clear-btn {
margin-left: auto;
}
.chat-header { .chat-header {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -82,22 +77,111 @@
flex-shrink: 0; flex-shrink: 0;
} }
.chat-clear-btn { /* ── Chat sub-header (History link + New Chat button) ────────────── */
.chat-subheader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 10px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.chat-history-link {
background: none;
border: none;
color: var(--text-muted);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.06em;
padding: 3px 0;
cursor: pointer;
transition: color 0.15s;
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-color: transparent;
}
.chat-history-link:hover,
.chat-history-link-active {
color: var(--text-primary);
text-decoration-color: currentColor;
}
.chat-new-btn {
background: none; background: none;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 4px; border-radius: 4px;
color: var(--text-muted); color: var(--text-muted);
font-size: 10px; font-size: 10px;
padding: 2px 7px; font-weight: 700;
letter-spacing: 0.06em;
padding: 3px 8px;
cursor: pointer; cursor: pointer;
transition: color 0.15s, border-color 0.15s; transition: color 0.15s, border-color 0.15s;
} }
.chat-clear-btn:hover { .chat-new-btn:hover {
color: var(--text-primary); color: var(--text-primary);
border-color: var(--text-muted); border-color: var(--text-muted);
} }
/* ── History list ────────────────────────────────────────────────── */
.chat-history-list {
flex: 1;
overflow-y: auto;
padding: 8px;
display: flex;
flex-direction: column;
gap: 2px;
}
.chat-history-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 3px;
padding: 9px 11px;
border-radius: 6px;
background: none;
border: 1px solid transparent;
cursor: pointer;
text-align: left;
width: 100%;
transition: background 0.12s, border-color 0.12s;
}
.chat-history-item:hover {
background: var(--message-bg);
}
.chat-history-item-active {
border-color: var(--border);
background: var(--message-bg);
}
.chat-history-time {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
}
.chat-history-summary {
font-size: 12px;
color: var(--text-muted);
line-height: 1.45;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.chat-history-item-active .chat-history-summary {
color: var(--text-secondary);
}
/* ── Pending attachment indicator (between tab bar and messages) ─── */ /* ── Pending attachment indicator (between tab bar and messages) ─── */
.chat-attachment-indicator { .chat-attachment-indicator {
display: flex; display: flex;

View File

@@ -9,9 +9,23 @@ import './Chat.css'
type TabId = 'chat' | 'feedback' type TabId = 'chat' | 'feedback'
function formatSessionTime(createdAt: number): string {
const date = new Date(createdAt)
const now = new Date()
const isToday = date.toDateString() === now.toDateString()
const yesterday = new Date(now)
yesterday.setDate(yesterday.getDate() - 1)
const isYesterday = date.toDateString() === yesterday.toDateString()
if (isToday) return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
if (isYesterday) return 'Yesterday'
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
}
export function ChatPanel(): JSX.Element { export function ChatPanel(): JSX.Element {
const { const {
chatHistory, chatHistory,
chatSessionsByFile,
activeSessionIdByFile,
isAILoading, isAILoading,
aiError, aiError,
activeFileContent, activeFileContent,
@@ -26,14 +40,19 @@ export function ChatPanel(): JSX.Element {
setAIError, setAIError,
setAnnotations, setAnnotations,
linkAnnotationsToMessage, linkAnnotationsToMessage,
clearChat newChat,
setActiveSession
} = useEditorStore() } = useEditorStore()
const [tab, setTab] = useState<TabId>('chat') const [tab, setTab] = useState<TabId>('chat')
const [showHistory, setShowHistory] = useState(false)
const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0) const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0)
const scrollRef = useRef<HTMLDivElement>(null) const scrollRef = useRef<HTMLDivElement>(null)
const prevAnnotationCountRef = useRef(annotations.length) const prevAnnotationCountRef = useRef(annotations.length)
// Collapse history when switching files
useEffect(() => { setShowHistory(false) }, [activeFilePath])
// Auto-switch to Feedback tab the first time annotations appear (0 → >0) // Auto-switch to Feedback tab the first time annotations appear (0 → >0)
useEffect(() => { useEffect(() => {
const prev = prevAnnotationCountRef.current const prev = prevAnnotationCountRef.current
@@ -103,6 +122,12 @@ export function ChatPanel(): JSX.Element {
const allAnnotationsForFile: TextAnnotation[] = const allAnnotationsForFile: TextAnnotation[] =
(activeFilePath ? annotationsByFile[activeFilePath]?.annotations : undefined) ?? [] (activeFilePath ? annotationsByFile[activeFilePath]?.annotations : undefined) ?? []
const sessions = (activeFilePath ? chatSessionsByFile[activeFilePath] : undefined) ?? []
const activeSessionId = activeFilePath ? activeSessionIdByFile[activeFilePath] : undefined
// Subheader is shown in the chat tab once there is at least one session
const showSubheader = tab === 'chat' && sessions.length > 0
return ( return (
<div className="chat-panel"> <div className="chat-panel">
{/* ── Tab bar ── */} {/* ── Tab bar ── */}
@@ -122,15 +147,25 @@ export function ChatPanel(): JSX.Element {
<span className="chat-tab-badge">{annotations.length}</span> <span className="chat-tab-badge">{annotations.length}</span>
)} )}
</button> </button>
{/* Clear button floated right, only visible in Chat tab */}
{tab === 'chat' && chatHistory.length > 0 && (
<button className="chat-clear-btn" onClick={clearChat} title="Clear conversation">
Clear
</button>
)}
</div> </div>
{/* ── Chat sub-header: History link + New Chat button ── */}
{showSubheader && (
<div className="chat-subheader">
<button
className={`chat-history-link${showHistory ? ' chat-history-link-active' : ''}`}
onClick={() => setShowHistory(v => !v)}
>
{showHistory ? 'Back to chat' : `History${sessions.length > 1 ? ` (${sessions.length})` : ''}`}
</button>
{!showHistory && chatHistory.length > 0 && (
<button className="chat-new-btn" onClick={newChat} title="Start a new conversation">
+ New Chat
</button>
)}
</div>
)}
{/* ── Pending-attachment indicator ── */} {/* ── Pending-attachment indicator ── */}
{pendingAttachmentCount > 0 && ( {pendingAttachmentCount > 0 && (
<div className="chat-attachment-indicator"> <div className="chat-attachment-indicator">
@@ -142,6 +177,29 @@ export function ChatPanel(): JSX.Element {
{/* ── Panel content ── */} {/* ── Panel content ── */}
{tab === 'feedback' ? ( {tab === 'feedback' ? (
<FeedbackPanel /> <FeedbackPanel />
) : showHistory ? (
<div className="chat-history-list">
{[...sessions].reverse().map((session) => {
const isActive = session.id === activeSessionId
const firstUserMsg = session.messages.find(m => m.role === 'user')
const summary = firstUserMsg
? firstUserMsg.content.slice(0, 80) + (firstUserMsg.content.length > 80 ? '…' : '')
: 'Empty conversation'
return (
<button
key={session.id}
className={`chat-history-item${isActive ? ' chat-history-item-active' : ''}`}
onClick={() => {
setActiveSession(session.id)
setShowHistory(false)
}}
>
<span className="chat-history-time">{formatSessionTime(session.createdAt)}</span>
<span className="chat-history-summary">{summary}</span>
</button>
)
})}
</div>
) : ( ) : (
<> <>
<div className="chat-messages" ref={scrollRef}> <div className="chat-messages" ref={scrollRef}>

View File

@@ -1,5 +1,5 @@
import { create } from 'zustand' import { create } from 'zustand'
import type { FileNode, ChatMessage, TextAnnotation, AnalysisMode, RevisionMeta, AttachmentMeta } from '../types/editor' import type { FileNode, ChatMessage, ChatSession, TextAnnotation, AnalysisMode, RevisionMeta, AttachmentMeta } from '../types/editor'
interface AnnotationFileState { interface AnnotationFileState {
mode: AnalysisMode mode: AnalysisMode
@@ -19,8 +19,9 @@ interface EditorState {
setContent: (content: string) => void setContent: (content: string) => void
markSaved: () => void markSaved: () => void
// Chat - persisted per file path // Chat - persisted per file path, multiple sessions per file
chatHistoryByFile: Record<string, ChatMessage[]> chatSessionsByFile: Record<string, ChatSession[]>
activeSessionIdByFile: Record<string, string>
chatHistory: ChatMessage[] chatHistory: ChatMessage[]
isAILoading: boolean isAILoading: boolean
aiError: string | null aiError: string | null
@@ -29,7 +30,8 @@ interface EditorState {
appendToLastAssistantMessage: (chunk: string) => void appendToLastAssistantMessage: (chunk: string) => void
setAILoading: (loading: boolean) => void setAILoading: (loading: boolean) => void
setAIError: (error: string | null) => void setAIError: (error: string | null) => void
clearChat: () => void newChat: () => void
setActiveSession: (sessionId: string) => void
// Annotations (highlights in editor) — also persisted per file // Annotations (highlights in editor) — also persisted per file
annotations: TextAnnotation[] annotations: TextAnnotation[]
@@ -128,7 +130,10 @@ export const useEditorStore = create<EditorState>((set, get) => ({
isDirty: false, isDirty: false,
setActiveFile: (path, content) => { setActiveFile: (path, content) => {
const s = get() const s = get()
const existing = s.chatHistoryByFile[path] ?? [] const sessions = s.chatSessionsByFile[path] ?? []
const activeId = s.activeSessionIdByFile[path]
const activeSession = sessions.find(sess => sess.id === activeId) ?? sessions[sessions.length - 1]
const existing = activeSession?.messages ?? []
const savedAnnotationState = s.annotationsByFile[path] const savedAnnotationState = s.annotationsByFile[path]
set({ set({
activeFilePath: path, activeFilePath: path,
@@ -143,7 +148,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -151,7 +157,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
setContent: (content) => set({ activeFileContent: content, isDirty: true }), setContent: (content) => set({ activeFileContent: content, isDirty: true }),
markSaved: () => set({ isDirty: false }), markSaved: () => set({ isDirty: false }),
chatHistoryByFile: {}, chatSessionsByFile: {},
activeSessionIdByFile: {},
chatHistory: [], chatHistory: [],
isAILoading: false, isAILoading: false,
aiError: null, aiError: null,
@@ -160,17 +167,30 @@ export const useEditorStore = create<EditorState>((set, get) => ({
const msg: ChatMessage = { id: `user-${Date.now()}`, role: 'user', content: text, attachments } const msg: ChatMessage = { id: `user-${Date.now()}`, role: 'user', content: text, attachments }
set((s) => { set((s) => {
const history = [...s.chatHistory, msg] const history = [...s.chatHistory, msg]
const byFile = s.activeFilePath if (!s.activeFilePath) return { chatHistory: history }
? { ...s.chatHistoryByFile, [s.activeFilePath]: history } let sessions = s.chatSessionsByFile[s.activeFilePath] ?? []
: s.chatHistoryByFile let activeId = s.activeSessionIdByFile[s.activeFilePath]
return { chatHistory: history, chatHistoryByFile: byFile } // If no session exists yet, create the first one
if (sessions.length === 0 || !activeId) {
const newSession: ChatSession = { id: `session-${Date.now()}`, createdAt: Date.now(), messages: history }
return {
chatHistory: history,
chatSessionsByFile: { ...s.chatSessionsByFile, [s.activeFilePath]: [newSession] },
activeSessionIdByFile: { ...s.activeSessionIdByFile, [s.activeFilePath]: newSession.id }
}
}
const updatedSessions = sessions.map(sess =>
sess.id === activeId ? { ...sess, messages: history } : sess
)
return { chatHistory: history, chatSessionsByFile: { ...s.chatSessionsByFile, [s.activeFilePath]: updatedSessions } }
}) })
scheduleSave(() => { scheduleSave(() => {
const st = get() const st = get()
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -180,10 +200,12 @@ export const useEditorStore = create<EditorState>((set, get) => ({
const msg: ChatMessage = { id: `asst-${Date.now()}`, role: 'assistant', content: '' } const msg: ChatMessage = { id: `asst-${Date.now()}`, role: 'assistant', content: '' }
set((s) => { set((s) => {
const history = [...s.chatHistory, msg] const history = [...s.chatHistory, msg]
const byFile = s.activeFilePath if (!s.activeFilePath) return { chatHistory: history }
? { ...s.chatHistoryByFile, [s.activeFilePath]: history } const activeId = s.activeSessionIdByFile[s.activeFilePath]
: s.chatHistoryByFile const sessions = (s.chatSessionsByFile[s.activeFilePath] ?? []).map(sess =>
return { chatHistory: history, chatHistoryByFile: byFile } sess.id === activeId ? { ...sess, messages: history } : sess
)
return { chatHistory: history, chatSessionsByFile: { ...s.chatSessionsByFile, [s.activeFilePath]: sessions } }
}) })
}, },
@@ -194,10 +216,12 @@ export const useEditorStore = create<EditorState>((set, get) => ({
if (last?.role === 'assistant') { if (last?.role === 'assistant') {
history[history.length - 1] = { ...last, content: last.content + chunk } history[history.length - 1] = { ...last, content: last.content + chunk }
} }
const byFile = s.activeFilePath if (!s.activeFilePath) return { chatHistory: history }
? { ...s.chatHistoryByFile, [s.activeFilePath]: history } const activeId = s.activeSessionIdByFile[s.activeFilePath]
: s.chatHistoryByFile const sessions = (s.chatSessionsByFile[s.activeFilePath] ?? []).map(sess =>
return { chatHistory: history, chatHistoryByFile: byFile } sess.id === activeId ? { ...sess, messages: history } : sess
)
return { chatHistory: history, chatSessionsByFile: { ...s.chatSessionsByFile, [s.activeFilePath]: sessions } }
}) })
}, },
@@ -210,7 +234,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -218,24 +243,41 @@ export const useEditorStore = create<EditorState>((set, get) => ({
}, },
setAIError: (aiError) => set({ aiError }), setAIError: (aiError) => set({ aiError }),
clearChat: () => { newChat: () => {
set((s) => { set((s) => {
const byFile = s.activeFilePath if (!s.activeFilePath) return {}
? { ...s.chatHistoryByFile, [s.activeFilePath]: [] } const newSession: ChatSession = { id: `session-${Date.now()}`, createdAt: Date.now(), messages: [] }
: s.chatHistoryByFile const existingSessions = s.chatSessionsByFile[s.activeFilePath] ?? []
return { chatHistory: [], chatHistoryByFile: byFile } return {
chatSessionsByFile: { ...s.chatSessionsByFile, [s.activeFilePath]: [...existingSessions, newSession] },
activeSessionIdByFile: { ...s.activeSessionIdByFile, [s.activeFilePath]: newSession.id },
chatHistory: []
}
}) })
scheduleSave(() => { scheduleSave(() => {
const st = get() const st = get()
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
}, },
setActiveSession: (sessionId: string) => {
set((s) => {
if (!s.activeFilePath) return {}
const session = (s.chatSessionsByFile[s.activeFilePath] ?? []).find(sess => sess.id === sessionId)
if (!session) return {}
return {
activeSessionIdByFile: { ...s.activeSessionIdByFile, [s.activeFilePath]: sessionId },
chatHistory: session.messages
}
})
},
annotations: [], annotations: [],
annotationsByFile: {}, annotationsByFile: {},
setAnnotations: (annotations) => { setAnnotations: (annotations) => {
@@ -255,7 +297,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -273,7 +316,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -290,7 +334,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -316,7 +361,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -327,17 +373,20 @@ export const useEditorStore = create<EditorState>((set, get) => ({
const history = s.chatHistory.map(m => const history = s.chatHistory.map(m =>
m.id === messageId ? { ...m, annotationIds } : m m.id === messageId ? { ...m, annotationIds } : m
) )
const byFile = s.activeFilePath if (!s.activeFilePath) return { chatHistory: history }
? { ...s.chatHistoryByFile, [s.activeFilePath]: history } const activeId = s.activeSessionIdByFile[s.activeFilePath]
: s.chatHistoryByFile const sessions = (s.chatSessionsByFile[s.activeFilePath] ?? []).map(sess =>
return { chatHistory: history, chatHistoryByFile: byFile } sess.id === activeId ? { ...sess, messages: history } : sess
)
return { chatHistory: history, chatSessionsByFile: { ...s.chatSessionsByFile, [s.activeFilePath]: sessions } }
}) })
scheduleSave(() => { scheduleSave(() => {
const st = get() const st = get()
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -361,7 +410,8 @@ export const useEditorStore = create<EditorState>((set, get) => ({
return { return {
activeFilePath: st.activeFilePath, activeFilePath: st.activeFilePath,
scrollPositions: st.scrollPositions, scrollPositions: st.scrollPositions,
chatHistoryByFile: st.chatHistoryByFile, chatSessionsByFile: st.chatSessionsByFile,
activeSessionIdByFile: st.activeSessionIdByFile,
annotationsByFile: st.annotationsByFile annotationsByFile: st.annotationsByFile
} }
}) })
@@ -402,8 +452,23 @@ export const useEditorStore = create<EditorState>((set, get) => ({
if (data.scrollPositions && typeof data.scrollPositions === 'object') { if (data.scrollPositions && typeof data.scrollPositions === 'object') {
patch.scrollPositions = data.scrollPositions as Record<string, number> patch.scrollPositions = data.scrollPositions as Record<string, number>
} }
if (data.chatHistoryByFile && typeof data.chatHistoryByFile === 'object') { if (data.chatSessionsByFile && typeof data.chatSessionsByFile === 'object') {
patch.chatHistoryByFile = data.chatHistoryByFile as Record<string, ChatMessage[]> patch.chatSessionsByFile = data.chatSessionsByFile as Record<string, ChatSession[]>
patch.activeSessionIdByFile = (data.activeSessionIdByFile as Record<string, string>) ?? {}
} else if (data.chatHistoryByFile && typeof data.chatHistoryByFile === 'object') {
// Migrate from old flat format — wrap each file's history in a single session
const legacy = data.chatHistoryByFile as Record<string, ChatMessage[]>
const sessions: Record<string, ChatSession[]> = {}
const activeIds: Record<string, string> = {}
for (const [path, messages] of Object.entries(legacy)) {
if (Array.isArray(messages) && messages.length > 0) {
const id = `session-migrated-${Date.now()}`
sessions[path] = [{ id, createdAt: Date.now(), messages }]
activeIds[path] = id
}
}
patch.chatSessionsByFile = sessions
patch.activeSessionIdByFile = activeIds
} }
if (data.annotationsByFile && typeof data.annotationsByFile === 'object') { if (data.annotationsByFile && typeof data.annotationsByFile === 'object') {
patch.annotationsByFile = data.annotationsByFile as Record<string, AnnotationFileState> patch.annotationsByFile = data.annotationsByFile as Record<string, AnnotationFileState>

View File

@@ -22,6 +22,12 @@ export interface ChatMessage {
annotationIds?: string[] // IDs of suggestions this message produced annotationIds?: string[] // IDs of suggestions this message produced
} }
export interface ChatSession {
id: string
createdAt: number
messages: ChatMessage[]
}
export type AnnotationType = 'passive_voice' | 'consistency' | 'style' | 'critique' | 'custom' export type AnnotationType = 'passive_voice' | 'consistency' | 'style' | 'critique' | 'custom'
export interface TextAnnotation { export interface TextAnnotation {