@wearesage/schema
Version:
A flexible schema definition and validation system for TypeScript with multi-database support
273 lines (231 loc) • 7.37 kB
text/typescript
import { Embeddings } from '../adapters/embeddings';
import { Entity, Property, Id, Index, Labels, Auth, ManyToMany, OneToMany } from '../core/decorators';
import { Message } from './Message';
import { PostgresMetadata } from '../adapters/metadata-layer';
import { Space } from './Space';
import { SpaceType } from '../test-types/space.types';
import { User } from './User';
export class Conversation extends Space {
// Explicit ID decorator to ensure inheritance works
id!: string;
// AI Model Configuration
model!: string; // e.g., "gemma3:12b-it-qat", "qwen3:1.7b"
temperature!: number; // 0.0 - 2.0
maxTokens?: number;
systemPrompt?: string; // Custom system prompt for this conversation
// Conversation Metadata
conversationType!: 'chat' | 'task' | 'brainstorm' | 'debug' | 'research';
messageCount!: number; // Denormalized for quick access
lastMessageAt?: Date; // Last activity timestamp
isArchived!: boolean; // Mark conversations as archived
tags?: string[]; // User-defined tags for organization
// Conversation Analytics
analytics?: {
totalTokensUsed?: number;
averageResponseTime?: number;
hasThinkingContent?: boolean;
modelSwitches?: number;
temperatureChanges?: number;
};
// Conversation Settings
settings?: {
verboseMode?: boolean;
autoSave?: boolean;
exportFormat?: 'json' | 'markdown' | 'txt';
};
// Conversation Status (extends Space status)
status!: 'active' | 'pending' | 'completed' | 'blocked' | 'failed';
// Participants in the conversation (required by Space)
participants!: User[];
// AI Models that can participate (array of model names)
aiModels?: string[]; // e.g., ["gemma3:12b-it-qat", "qwen3:1.7b"]
// Active speaker/model (who's currently responding)
activeSpeaker?: {
type: 'human' | 'ai';
id: string; // userId or model name
};
// Messages in this conversation
messages?: Message[];
// Semantic embedding for similarity search
semanticEmbedding?: number[];
// Constructor with defaults
constructor() {
super();
this.messageCount = 0;
this.isArchived = false;
this.status = 'active';
this.conversationType = 'chat';
this.temperature = 0.7;
// Set space type for this space
this.spaceType = SpaceType.CONVERSATION;
}
// Helper methods for conversation management
incrementMessageCount(): void {
this.messageCount++;
this.lastMessageAt = new Date();
}
updateAnalytics(tokenCount?: number, responseTime?: number, hasThinking?: boolean): void {
if (!this.analytics) {
this.analytics = {};
}
if (tokenCount) {
this.analytics.totalTokensUsed = (this.analytics.totalTokensUsed || 0) + tokenCount;
}
if (responseTime) {
const currentAvg = this.analytics.averageResponseTime || 0;
const count = this.messageCount;
this.analytics.averageResponseTime = (currentAvg * (count - 1) + responseTime) / count;
}
if (hasThinking !== undefined) {
this.analytics.hasThinkingContent = this.analytics.hasThinkingContent || hasThinking;
}
}
addTag(tag: string): void {
if (!this.tags) {
this.tags = [];
}
if (!this.tags.includes(tag)) {
this.tags.push(tag);
}
}
removeTag(tag: string): void {
if (this.tags) {
this.tags = this.tags.filter(t => t !== tag);
}
}
archive(): void {
this.isArchived = true;
this.status = 'completed';
}
pause(): void {
this.status = 'blocked';
}
resume(): void {
this.status = 'active';
}
complete(): void {
this.status = 'completed';
}
// Multi-participant conversation management
addParticipant(user: User): void {
if (!this.participants) {
this.participants = [];
}
if (!this.participants.find(p => p.id === user.id)) {
this.participants.push(user);
}
}
addAiModel(modelName: string): void {
if (!this.aiModels) {
this.aiModels = [];
}
if (!this.aiModels.includes(modelName)) {
this.aiModels.push(modelName);
}
}
removeAiModel(modelName: string): void {
if (this.aiModels) {
this.aiModels = this.aiModels.filter(m => m !== modelName);
}
}
setActiveSpeaker(type: 'human' | 'ai', id: string): void {
this.activeSpeaker = { type, id };
}
getAllParticipants(): Array<{type: 'human' | 'ai', id: string, displayName?: string}> {
const participants: Array<{type: 'human' | 'ai', id: string, displayName?: string}> = [];
// Add human participants
if (this.participants) {
this.participants.forEach(user => {
participants.push({
type: 'human',
id: user.id,
displayName: user.name
});
});
}
// Add AI models
if (this.aiModels) {
this.aiModels.forEach(model => {
participants.push({
type: 'ai',
id: model,
displayName: model
});
});
}
return participants;
}
// Generate a smart title based on conversation content
generateSmartTitle(firstUserMessage?: string): string {
if (firstUserMessage) {
// Extract key concepts from first message
const cleanMessage = firstUserMessage.trim().toLowerCase();
if (cleanMessage.includes('help') || cleanMessage.includes('how')) {
return `Help: ${firstUserMessage.substring(0, 30)}...`;
} else if (cleanMessage.includes('debug') || cleanMessage.includes('error')) {
return `Debug: ${firstUserMessage.substring(0, 30)}...`;
} else if (cleanMessage.includes('create') || cleanMessage.includes('build')) {
return `Build: ${firstUserMessage.substring(0, 30)}...`;
} else {
return `Chat: ${firstUserMessage.substring(0, 30)}...`;
}
}
return `${this.conversationType.charAt(0).toUpperCase()}${this.conversationType.slice(1)} - ${new Date().toLocaleDateString()}`;
}
// Get conversation summary for listings
getSummary(): {
id: string;
title: string;
messageCount: number;
model: string;
lastActivity: Date | undefined;
status: string;
tags: string[];
} {
return {
id: this.id,
title: this.name,
messageCount: this.messageCount,
model: this.model,
lastActivity: this.lastMessageAt,
status: this.status,
tags: this.tags || []
};
}
}