@kaifronsdal/transcript-viewer
Version:
A web-based viewer for AI conversation transcripts with rollback support
410 lines (349 loc) • 11.7 kB
text/typescript
// Base types that match the Python Pydantic models
import type { Operation } from 'fast-json-patch';
// Enums for better type safety and intellisense
export const MessageType = {
SYSTEM: 'system',
ASSISTANT: 'assistant',
USER: 'user',
TOOL: 'tool',
API_FAILURE: 'api_failure',
INFO: 'info'
} as const;
export const ErrorCategory = {
RATE_LIMIT: 'rate_limit',
TIMEOUT: 'timeout',
QUOTA_BILLING: 'quota_billing',
AUTHENTICATION: 'authentication',
CONTENT_FILTER: 'content_filter',
EMPTY_CONTENT: 'empty_content',
GENERIC_FAILURE: 'generic_failure',
MODEL_NOT_FOUND: 'model_not_found',
INVALID_REQUEST: 'invalid_request'
} as const;
export const EditOperation = {
ADD: 'add',
ROLLBACK: 'rollback',
RESET: 'reset',
JSON_PATCH: 'json_patch'
} as const;
export const EventType = {
TRANSCRIPT_EVENT: 'transcript_event',
TOOL_CREATION_EVENT: 'tool_creation_event',
INFO_EVENT: 'info_event',
DECISION_EVENT: 'decision_event'
} as const;
export type MessageTypeValue = typeof MessageType[keyof typeof MessageType];
export type ErrorCategoryValue = typeof ErrorCategory[keyof typeof ErrorCategory];
export type EditOperationValue = typeof EditOperation[keyof typeof EditOperation];
export type EventTypeValue = typeof EventType[keyof typeof EventType];
// Schema-accurate ToolCall shape (with backward-compat optional fields)
export interface ToolCallContent {
title?: string | null;
format: 'text' | 'markdown';
content: string;
}
export type ToolCallErrorType =
| 'parsing'
| 'timeout'
| 'unicode_decode'
| 'permission'
| 'file_not_found'
| 'is_a_directory'
| 'limit'
| 'approval'
| 'unknown'
| 'output_limit';
export interface ToolCallError {
type: ToolCallErrorType;
message: string;
}
export interface ToolCall {
id: string;
function: string;
arguments: Record<string, any>;
internal?: any;
parse_error?: ToolCallError | null;
view?: ToolCallContent | null;
type?: string | null;
}
// Base message interface with common fields
interface BaseMessage {
content: string | Array<string | Record<string, any>>;
name?: string;
id?: string;
metadata?: Record<string, any>;
// UI-specific properties
isShared?: boolean;
viewSource?: string | string[];
eventId?: string; // ID of the event that created this message
}
// Discriminated union for different message types (normalized)
export interface SystemMessage extends BaseMessage { type: typeof MessageType.SYSTEM; }
export interface UserMessage extends BaseMessage { type: typeof MessageType.USER; }
export interface AssistantMessage extends BaseMessage { type: typeof MessageType.ASSISTANT; tool_calls?: ToolCall[]; reasoning?: string; }
export interface ToolMessage extends BaseMessage {
type: typeof MessageType.TOOL;
tool_call_id?: string | null;
function?: string | null;
error?: ToolCallError | null;
}
export interface APIFailureMessage {
type: typeof MessageType.API_FAILURE;
id: string;
error_message: string;
error_category: ErrorCategoryValue;
recoverable?: boolean;
// UI-specific properties
isShared?: boolean;
viewSource?: string | string[];
eventId?: string;
}
export interface InfoMessage {
type: 'info';
id?: string;
info: string | Record<string, any>;
timestamp?: string;
// UI-specific properties
isShared?: boolean;
viewSource?: string | string[];
eventId?: string;
}
export type Message = SystemMessage | UserMessage | AssistantMessage | ToolMessage | APIFailureMessage | InfoMessage;
// Raw role-based messages from schema (v3)
export type RawChatMessage =
| {
role: 'system';
content: string | Array<string | Record<string, any>>;
id?: string | null;
source?: 'input' | 'generate' | null;
metadata?: Record<string, any> | null;
internal?: any | null;
}
| {
role: 'user';
content: string | Array<string | Record<string, any>>;
id?: string | null;
source?: 'input' | 'generate' | null;
metadata?: Record<string, any> | null;
internal?: any | null;
tool_call_id?: string[] | null;
}
| {
role: 'assistant';
content: string | Array<string | Record<string, any>>;
id?: string | null;
source?: 'input' | 'generate' | null;
metadata?: Record<string, any> | null;
internal?: any | null;
tool_calls?: ToolCall[] | null;
model?: string | null;
}
| {
role: 'tool';
content: string | Array<string | Record<string, any>>;
id?: string | null;
source?: 'input' | 'generate' | null;
metadata?: Record<string, any> | null;
internal?: any | null;
tool_call_id?: string | null;
function?: string | null;
error?: ToolCallError | null;
};
// Keep the old APIFailure interface for backward compatibility
export type APIFailure = APIFailureMessage;
// Discriminated union for edit operations (schema-driven)
export interface AddMessage { operation: typeof EditOperation.ADD; message: RawChatMessage; }
export interface Rollback {
operation: typeof EditOperation.ROLLBACK;
count: number;
to_id?: string;
}
export interface Reset { operation: typeof EditOperation.RESET; new_messages?: RawChatMessage[]; }
export interface JSONPatchEdit {
operation: typeof EditOperation.JSON_PATCH;
patch: Operation[];
name?: string | null;
}
export type Edit = AddMessage | Rollback | Reset | JSONPatchEdit;
// Base event interface
interface BaseEvent {
id?: string;
metadata?: Record<string, any>;
timestamp?: string;
}
export interface TranscriptEvent extends BaseEvent {
type: typeof EventType.TRANSCRIPT_EVENT;
view: string | string[];
edit: Edit;
}
export interface InfoEvent extends BaseEvent {
type: typeof EventType.INFO_EVENT;
info: string | Record<string, any>;
}
export interface ToolDefinition {
name: string;
description: string;
parameters: ToolParams;
parallel?: boolean;
options?: Record<string, any> | null;
viewer?: any | null;
model_input?: any | null;
}
// JSON Schema subset for tool params
export interface JSONSchemaDef {
type?: 'string' | 'integer' | 'number' | 'boolean' | 'array' | 'object' | 'null' | null;
format?: string | null;
description?: string | null;
default?: any;
enum?: any[] | null;
items?: JSONSchemaDef | null;
properties?: Record<string, JSONSchemaDef> | null;
additionalProperties?: JSONSchemaDef | boolean | null;
anyOf?: JSONSchemaDef[] | null;
required?: string[] | null;
}
export interface ToolParams {
type: 'object';
properties: Record<string, JSONSchemaDef>;
required?: string[];
additionalProperties?: boolean;
}
export interface ToolCreationEvent extends BaseEvent {
type: typeof EventType.TOOL_CREATION_EVENT;
model: string;
tool: ToolDefinition;
}
export interface DecisionEvent extends BaseEvent {
type: typeof EventType.DECISION_EVENT;
content: any;
}
export type Events = TranscriptEvent | ToolCreationEvent | InfoEvent | DecisionEvent;
export interface JudgeOutput {
response: string;
summary: string;
justification?: string;
scores: Record<string, number>;
score_descriptions?: Record<string, string>;
}
export interface TranscriptMetadata {
transcript_id: string;
auditor_model?: string;
target_model?: string;
created_at: string;
updated_at: string;
version?: 'v3.0';
description?: string;
short_name?: string;
tags?: string[];
judge_output?: JudgeOutput;
}
export interface Transcript {
metadata: TranscriptMetadata;
events?: Events[];
messages?: any[];
target_messages?: any[];
}
// Split display types for clarity and correctness
export interface TranscriptDisplayMeta {
id: string;
model: string;
split: string;
concerningScore: number;
summary: string;
scores: Record<string, number>;
scoreDescriptions?: Record<string, string>;
judgeSummary: string;
justification: string;
tags?: string[];
systemPrompt?: string;
_filePath?: string;
}
export interface TranscriptDisplayFull extends TranscriptDisplayMeta {
transcript: Transcript;
}
// Backward-compatible alias used across the app
export type TranscriptDisplay = TranscriptDisplayMeta | TranscriptDisplayFull;
// Filter and view state types
export interface FilterState {
filterExpression: string;
searchQuery: string;
}
export interface ViewSettings {
viewMode: 'tree' | 'list';
}
export interface TranscriptViewSettings {
selectedView: string;
showApiFailures: boolean;
showSharedHistory: boolean;
showSystemPrompt: boolean;
availableViews: string[];
}
// Type guards for better runtime type checking
export const isSystemMessage = (message: Message): message is SystemMessage =>
message.type === MessageType.SYSTEM;
export const isUserMessage = (message: Message): message is UserMessage =>
message.type === MessageType.USER;
export const isAssistantMessage = (message: Message): message is AssistantMessage =>
message.type === MessageType.ASSISTANT;
export const isToolMessage = (message: Message): message is ToolMessage =>
message.type === MessageType.TOOL;
export const isAPIFailureMessage = (message: Message): message is APIFailureMessage =>
message.type === MessageType.API_FAILURE;
export const isInfoMessage = (message: Message): message is InfoMessage =>
message.type === MessageType.INFO;
export const isTranscriptEvent = (event: Events): event is TranscriptEvent =>
event.type === EventType.TRANSCRIPT_EVENT;
export const isToolCreationEvent = (event: Events): event is ToolCreationEvent =>
event.type === EventType.TOOL_CREATION_EVENT;
export const isInfoEvent = (event: Events): event is InfoEvent =>
event.type === EventType.INFO_EVENT;
export const isDecisionEvent = (event: Events): event is DecisionEvent =>
event.type === EventType.DECISION_EVENT;
export const isAddOperation = (edit: Edit): edit is AddMessage =>
edit.operation === EditOperation.ADD;
export const isRollbackOperation = (edit: Edit): edit is Rollback =>
edit.operation === EditOperation.ROLLBACK;
export const isResetOperation = (edit: Edit): edit is Reset =>
edit.operation === EditOperation.RESET;
export const isJsonPatchOperation = (edit: Edit): edit is JSONPatchEdit =>
edit.operation === EditOperation.JSON_PATCH;
// Unified TanStack Table-compatible row type following the official nested sub-row pattern
// https://tanstack.com/table/v8/docs/guide/data#nested-sub-row-data
export interface TableRow {
// Required fields for all rows
id: string;
name: string;
path: string;
type: 'folder' | 'transcript';
// TanStack Table recursive sub-rows (following official pattern)
subRows?: TableRow[];
// Folder-specific fields
isEmpty?: boolean;
transcriptCount?: number;
// Transcript data fields (only present when type === 'transcript')
model?: string;
split?: string;
summary?: string;
scores?: Record<string, number>;
scoreDescriptions?: Record<string, string>;
concerningScore?: number;
judgeSummary?: string;
justification?: string;
tags?: string[];
originalTranscript?: TranscriptDisplay;
}
// Legacy aliases for backward compatibility during migration
export type TreeNode = TableRow;
export type FolderNode = TableRow & { type: 'folder' };
export type TranscriptNode = TableRow & { type: 'transcript' };
// Legacy alias for backward compatibility during migration
export type FolderNodeWithStats = FolderNode;
// Directory and loading types (shared between client and server)
export interface DirectoryInfo {
path: string;
relativePath: string;
isEmpty: boolean;
hasTranscripts: boolean;
transcriptCount: number;
subdirectoryCount: number;
}