Files
CxIDE/Models/Models.swift
T
cx-git-agent 6218a6ef28 feat: syntax highlighting, workspace search, git-tree wiring, auto-restore
- NSTextView-based syntax highlighting with regex tokenizer
  - Swift, C/C++, ObjC, JSON keywords, types, strings, comments
  - Theme-aware coloring, debounced re-highlighting
- Workspace-wide search across all source files
  - Grouped results by file with line numbers
  - Scope toggle: current file vs all files
- Git status badges on file tree nodes
  - GitService changes flow to FileNode.gitStatus
- Agent file operations refresh file tree
- Auto-restore last workspace on launch
- All tests passing (0 errors, 0 warnings)
2026-04-21 16:38:42 -05:00

785 lines
26 KiB
Swift

import Foundation
import SwiftUI
// MARK: - VSCode Dark+ Color Constants
enum VSC {
static let editorBg = Color(red: 0.118, green: 0.118, blue: 0.118)
static let sidebarBg = Color(red: 0.145, green: 0.145, blue: 0.149)
static let activityBarBg = Color(red: 0.20, green: 0.20, blue: 0.20)
static let tabBarBg = Color(red: 0.176, green: 0.176, blue: 0.176)
static let activeTabBg = Color(red: 0.118, green: 0.118, blue: 0.118)
static let statusBarBg = Color(red: 0.0, green: 0.478, blue: 0.8)
static let statusBarNoFolder = Color(red: 0.404, green: 0.227, blue: 0.718)
static let panelBg = Color(red: 0.118, green: 0.118, blue: 0.118)
static let titleBarBg = Color(red: 0.188, green: 0.188, blue: 0.192)
static let border = Color(red: 0.235, green: 0.235, blue: 0.235)
static let text = Color(white: 0.85)
static let dimText = Color(white: 0.55)
static let accentBlue = Color(red: 0.0, green: 0.478, blue: 0.8)
static let focusBorder = Color(red: 0.0, green: 0.478, blue: 0.8)
static let activityActive = Color.white
static let activityInactive = Color(white: 0.5)
static let badgeBg = Color(red: 0.0, green: 0.478, blue: 0.8)
static let inputBg = Color(red: 0.188, green: 0.188, blue: 0.188)
static let inputBorder = Color(red: 0.27, green: 0.27, blue: 0.27)
static let listHover = Color(white: 0.18)
static let listActive = Color(red: 0.02, green: 0.35, blue: 0.55)
static let errorFg = Color(red: 0.94, green: 0.31, blue: 0.27)
static let warningFg = Color(red: 0.80, green: 0.69, blue: 0.26)
static let breadcrumbFg = Color(white: 0.6)
static let gutterBg = Color(red: 0.118, green: 0.118, blue: 0.118)
static let gutterFg = Color(white: 0.35)
static let lineHighlight = Color(white: 0.165)
static let selection = Color(red: 0.149, green: 0.310, blue: 0.471)
static let minimapBg = Color(red: 0.118, green: 0.118, blue: 0.118)
static let scrollbar = Color(white: 0.4).opacity(0.4)
}
// MARK: - Error Types
enum IDEErrorCode: Error, LocalizedError {
case fileNotFound
case compilerError(String)
case runtimePanic
case engineUnavailable
case fileAccessDenied(String)
case invalidEncoding
var errorDescription: String? {
switch self {
case .fileNotFound: return "File not found."
case .compilerError(let msg): return "Compiler error: \(msg)"
case .runtimePanic: return "Runtime panic occurred."
case .engineUnavailable: return "Code engine is unavailable."
case .fileAccessDenied(let path): return "Access denied: \(path)"
case .invalidEncoding: return "Invalid file encoding."
}
}
}
// MARK: - Diagnostics
struct Diagnostic: Identifiable {
let id = UUID()
let line: Int
let column: Int
let message: String
let severity: DiagnosticSeverity
enum DiagnosticSeverity: String, CaseIterable {
case error, warning, info
var iconName: String {
switch self {
case .error: return "xmark.circle.fill"
case .warning: return "exclamationmark.triangle.fill"
case .info: return "info.circle.fill"
}
}
var color: Color {
switch self {
case .error: return .red
case .warning: return .yellow
case .info: return .blue
}
}
}
}
// MARK: - Console
struct ConsoleEntry: Identifiable {
let id = UUID()
let timestamp: Date
let message: String
let level: Level
enum Level: String, CaseIterable {
case output, error, system, debug, stdin, agent
var color: Color {
switch self {
case .output: return .green
case .error: return .red
case .system: return .cyan
case .debug: return .gray
case .stdin: return .orange
case .agent: return .purple
}
}
var iconName: String {
switch self {
case .output: return "arrow.right"
case .error: return "xmark.circle"
case .system: return "gearshape"
case .debug: return "ant"
case .stdin: return "keyboard"
case .agent: return "bubble.left"
}
}
}
var formattedTimestamp: String {
Self.timestampFormatter.string(from: timestamp)
}
private static let timestampFormatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "HH:mm:ss.SSS"
return f
}()
}
// MARK: - Editor Tabs
struct EditorTab: Identifiable, Hashable {
let id: UUID
let name: String
let url: URL?
var content: String
var isModified: Bool
var language: Language
var cursorLine: Int
var cursorColumn: Int
init(name: String, url: URL? = nil, content: String = "", isModified: Bool = false,
language: Language = .swift, cursorLine: Int = 1, cursorColumn: Int = 1) {
self.id = UUID()
self.name = name
self.url = url
self.content = content
self.isModified = isModified
self.language = language
self.cursorLine = cursorLine
self.cursorColumn = cursorColumn
}
func hash(into hasher: inout Hasher) { hasher.combine(id) }
static func == (lhs: EditorTab, rhs: EditorTab) -> Bool { lhs.id == rhs.id }
enum Language: String, CaseIterable {
case swift, objc, cpp, c, header, json, markdown, plaintext
var displayName: String {
switch self {
case .swift: return "Swift"
case .objc: return "Objective-C"
case .cpp: return "C++"
case .c: return "C"
case .header: return "Header"
case .json: return "JSON"
case .markdown: return "Markdown"
case .plaintext: return "Plain Text"
}
}
var iconColor: Color {
switch self {
case .swift: return .orange
case .objc: return .teal
case .cpp: return .cyan
case .c: return .blue
case .header: return .purple
case .json: return .yellow
case .markdown: return .green
case .plaintext: return .gray
}
}
static func from(extension ext: String) -> Language {
switch ext.lowercased() {
case "swift": return .swift
case "m", "mm": return .objc
case "cpp", "cc", "cxx": return .cpp
case "c": return .c
case "h", "hpp": return .header
case "json": return .json
case "md", "markdown": return .markdown
default: return .plaintext
}
}
}
}
// MARK: - Search & Replace
struct SearchState {
var query: String = ""
var replacement: String = ""
var isActive: Bool = false
var isCaseSensitive: Bool = false
var isRegex: Bool = false
var isWholeWord: Bool = false
var matchCount: Int = 0
var currentMatchIndex: Int = 0
var scope: SearchScope = .currentFile
enum SearchScope: String, CaseIterable {
case currentFile = "Current File"
case allFiles = "All Files"
}
var hasMatches: Bool { matchCount > 0 }
}
// MARK: - Workspace Search Result
struct WorkspaceSearchResult: Identifiable {
let id = UUID()
let fileURL: URL
let fileName: String
let line: Int
let column: Int
let lineContent: String
let matchRange: Range<String.Index>
}
// MARK: - Theme
struct IDETheme: Identifiable, Hashable {
let id: String
let name: String
let editorBackground: Color
let editorForeground: Color
let gutterBackground: Color
let gutterForeground: Color
let sidebarBackground: Color
let toolbarBackground: Color
let statusBarBackground: Color
let selectionColor: Color
let currentLineColor: Color
let keywordColor: Color
let stringColor: Color
let commentColor: Color
let numberColor: Color
let typeColor: Color
let functionColor: Color
let preprocessorColor: Color
func hash(into hasher: inout Hasher) { hasher.combine(id) }
static func == (lhs: IDETheme, rhs: IDETheme) -> Bool { lhs.id == rhs.id }
static let midnight = IDETheme(
id: "midnight", name: "Midnight",
editorBackground: Color(red: 0.06, green: 0.06, blue: 0.10),
editorForeground: Color(white: 0.92),
gutterBackground: Color(red: 0.05, green: 0.05, blue: 0.08),
gutterForeground: Color(white: 0.35),
sidebarBackground: Color(red: 0.07, green: 0.07, blue: 0.09),
toolbarBackground: Color(red: 0.08, green: 0.08, blue: 0.10),
statusBarBackground: Color(red: 0.06, green: 0.06, blue: 0.08),
selectionColor: Color.blue.opacity(0.3),
currentLineColor: Color(white: 0.12),
keywordColor: Color(red: 0.85, green: 0.35, blue: 0.65),
stringColor: Color(red: 0.90, green: 0.40, blue: 0.30),
commentColor: Color(red: 0.45, green: 0.55, blue: 0.40),
numberColor: Color(red: 0.85, green: 0.75, blue: 0.40),
typeColor: Color(red: 0.55, green: 0.75, blue: 0.85),
functionColor: Color(red: 0.40, green: 0.70, blue: 0.90),
preprocessorColor: Color(red: 0.70, green: 0.50, blue: 0.80)
)
static let xcodeDark = IDETheme(
id: "xcode_dark", name: "Xcode Dark",
editorBackground: Color(red: 0.11, green: 0.12, blue: 0.14),
editorForeground: Color(white: 0.90),
gutterBackground: Color(red: 0.10, green: 0.10, blue: 0.12),
gutterForeground: Color(white: 0.40),
sidebarBackground: Color(red: 0.12, green: 0.12, blue: 0.14),
toolbarBackground: Color(red: 0.14, green: 0.14, blue: 0.16),
statusBarBackground: Color(red: 0.10, green: 0.10, blue: 0.12),
selectionColor: Color.blue.opacity(0.25),
currentLineColor: Color(white: 0.16),
keywordColor: Color(red: 0.99, green: 0.37, blue: 0.53),
stringColor: Color(red: 0.99, green: 0.42, blue: 0.33),
commentColor: Color(red: 0.42, green: 0.56, blue: 0.37),
numberColor: Color(red: 0.85, green: 0.75, blue: 0.45),
typeColor: Color(red: 0.36, green: 0.85, blue: 0.76),
functionColor: Color(red: 0.40, green: 0.72, blue: 0.92),
preprocessorColor: Color(red: 0.68, green: 0.51, blue: 0.87)
)
static let solarized = IDETheme(
id: "solarized", name: "Solarized Dark",
editorBackground: Color(red: 0.00, green: 0.17, blue: 0.21),
editorForeground: Color(red: 0.51, green: 0.58, blue: 0.59),
gutterBackground: Color(red: 0.00, green: 0.15, blue: 0.19),
gutterForeground: Color(red: 0.35, green: 0.43, blue: 0.46),
sidebarBackground: Color(red: 0.00, green: 0.14, blue: 0.18),
toolbarBackground: Color(red: 0.02, green: 0.19, blue: 0.23),
statusBarBackground: Color(red: 0.00, green: 0.13, blue: 0.17),
selectionColor: Color(red: 0.07, green: 0.26, blue: 0.30),
currentLineColor: Color(red: 0.02, green: 0.21, blue: 0.25),
keywordColor: Color(red: 0.52, green: 0.60, blue: 0.00),
stringColor: Color(red: 0.16, green: 0.63, blue: 0.60),
commentColor: Color(red: 0.35, green: 0.43, blue: 0.46),
numberColor: Color(red: 0.80, green: 0.29, blue: 0.09),
typeColor: Color(red: 0.15, green: 0.55, blue: 0.82),
functionColor: Color(red: 0.42, green: 0.44, blue: 0.77),
preprocessorColor: Color(red: 0.83, green: 0.21, blue: 0.51)
)
static let allThemes: [IDETheme] = [.vscodeDark, .midnight, .xcodeDark, .solarized]
static let vscodeDark = IDETheme(
id: "vscode_dark", name: "Dark+ (VSCode)",
editorBackground: Color(red: 0.118, green: 0.118, blue: 0.118),
editorForeground: Color(red: 0.82, green: 0.82, blue: 0.82),
gutterBackground: Color(red: 0.118, green: 0.118, blue: 0.118),
gutterForeground: Color(white: 0.35),
sidebarBackground: Color(red: 0.145, green: 0.145, blue: 0.149),
toolbarBackground: Color(red: 0.176, green: 0.176, blue: 0.176),
statusBarBackground: Color(red: 0.0, green: 0.478, blue: 0.8),
selectionColor: Color(red: 0.149, green: 0.310, blue: 0.471),
currentLineColor: Color(red: 0.165, green: 0.176, blue: 0.180),
keywordColor: Color(red: 0.337, green: 0.612, blue: 0.839),
stringColor: Color(red: 0.808, green: 0.569, blue: 0.471),
commentColor: Color(red: 0.416, green: 0.600, blue: 0.333),
numberColor: Color(red: 0.710, green: 0.808, blue: 0.659),
typeColor: Color(red: 0.306, green: 0.788, blue: 0.690),
functionColor: Color(red: 0.863, green: 0.863, blue: 0.667),
preprocessorColor: Color(red: 0.773, green: 0.525, blue: 0.753)
)
}
// MARK: - Build Configuration
struct BuildConfiguration: Identifiable, Hashable {
let id: String
let name: String
var arguments: [String]
var environmentVariables: [String: String]
var workingDirectory: URL?
var optimizationLevel: OptimizationLevel
enum OptimizationLevel: String, CaseIterable {
case debug = "-Onone"
case release = "-O"
case unchecked = "-Ounchecked"
var displayName: String {
switch self {
case .debug: return "Debug (-Onone)"
case .release: return "Release (-O)"
case .unchecked: return "Unchecked (-Ounchecked)"
}
}
}
func hash(into hasher: inout Hasher) { hasher.combine(id) }
static func == (lhs: BuildConfiguration, rhs: BuildConfiguration) -> Bool { lhs.id == rhs.id }
static let `default` = BuildConfiguration(
id: "default", name: "Debug",
arguments: [], environmentVariables: [:],
workingDirectory: nil, optimizationLevel: .debug
)
}
// MARK: - Snippets
struct CodeSnippet: Identifiable, Hashable {
let id: String
let title: String
let code: String
let language: EditorTab.Language
let category: String
static let builtIn: [CodeSnippet] = [
CodeSnippet(id: "hello", title: "Hello World", code: "print(\"Hello, World!\")", language: .swift, category: "Basics"),
CodeSnippet(id: "func", title: "Function", code: "func name(param: Type) -> ReturnType {\n \n}", language: .swift, category: "Basics"),
CodeSnippet(id: "struct", title: "Struct", code: "struct Name {\n let property: Type\n}", language: .swift, category: "Types"),
CodeSnippet(id: "class", title: "Class", code: "class Name {\n init() {\n \n }\n}", language: .swift, category: "Types"),
CodeSnippet(id: "enum", title: "Enum", code: "enum Name {\n case first\n case second\n}", language: .swift, category: "Types"),
CodeSnippet(id: "protocol", title: "Protocol", code: "protocol Name {\n func method()\n}", language: .swift, category: "Types"),
CodeSnippet(id: "guard", title: "Guard Let", code: "guard let value = optional else {\n return\n}", language: .swift, category: "Control Flow"),
CodeSnippet(id: "iflet", title: "If Let", code: "if let value = optional {\n \n}", language: .swift, category: "Control Flow"),
CodeSnippet(id: "forin", title: "For-In Loop", code: "for item in collection {\n \n}", language: .swift, category: "Control Flow"),
CodeSnippet(id: "switch", title: "Switch", code: "switch value {\ncase .first:\n break\ncase .second:\n break\n}", language: .swift, category: "Control Flow"),
CodeSnippet(id: "docatch", title: "Do-Catch", code: "do {\n try expression\n} catch {\n print(error)\n}", language: .swift, category: "Error Handling"),
CodeSnippet(id: "async_func", title: "Async Function", code: "func fetch() async throws -> Data {\n \n}", language: .swift, category: "Concurrency"),
CodeSnippet(id: "task", title: "Task", code: "Task {\n await doWork()\n}", language: .swift, category: "Concurrency"),
CodeSnippet(id: "closure", title: "Closure", code: "{ [weak self] param in\n guard let self else { return }\n \n}", language: .swift, category: "Closures"),
CodeSnippet(id: "ext", title: "Extension", code: "extension TypeName {\n \n}", language: .swift, category: "Types"),
]
}
// MARK: - Breakpoint
struct Breakpoint: Identifiable, Hashable {
let id = UUID()
let file: String
var line: Int
var isEnabled: Bool = true
var condition: String? = nil
func hash(into hasher: inout Hasher) { hasher.combine(id) }
static func == (lhs: Breakpoint, rhs: Breakpoint) -> Bool { lhs.id == rhs.id }
}
// MARK: - Execution History
struct ExecutionRecord: Identifiable {
let id = UUID()
let timestamp: Date
let source: String
let output: String
let exitCode: Int32
let duration: TimeInterval
var succeeded: Bool { exitCode == 0 }
var formattedTimestamp: String {
Self.formatter.string(from: timestamp)
}
var formattedDuration: String {
String(format: "%.2fs", duration)
}
private static let formatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "MMM d, HH:mm:ss"
return f
}()
}
// MARK: - Activity Panel
enum ActivityPanel: String, CaseIterable {
case explorer = "Explorer"
case search = "Search"
case git = "Source Control"
case snippets = "Snippets"
case debug = "Debug"
case agent = "Agent"
case chat = "Chat"
case settings = "Settings"
var iconName: String {
switch self {
case .explorer: return "doc.on.doc"
case .search: return "magnifyingglass"
case .git: return "point.3.filled.connected.trianglepath.dotted"
case .snippets: return "text.insert"
case .debug: return "ant"
case .agent: return "bubble.left.and.text.bubble.right"
case .chat: return "message"
case .settings: return "gearshape"
}
}
}
// MARK: - Command Palette
struct CommandItem: Identifiable {
let id = UUID()
let title: String
let subtitle: String?
let shortcut: String?
let icon: String
let action: () -> Void
init(title: String, subtitle: String? = nil, shortcut: String? = nil, icon: String = "command", action: @escaping () -> Void) {
self.title = title
self.subtitle = subtitle
self.shortcut = shortcut
self.icon = icon
self.action = action
}
}
// MARK: - Agent Types
struct AgentMessage: Identifiable {
let id = UUID()
let role: Role
let content: String
let timestamp: Date
var toolName: String?
var isStreaming: Bool
var isError: Bool
enum Role: String {
case user, agent, system, tool
var color: Color {
switch self {
case .user: return .blue
case .agent: return .purple
case .system: return .cyan
case .tool: return .orange
}
}
var iconName: String {
switch self {
case .user: return "person.fill"
case .agent: return "cpu"
case .system: return "gearshape.fill"
case .tool: return "wrench.fill"
}
}
}
init(role: Role, content: String, toolName: String? = nil,
isStreaming: Bool = false, isError: Bool = false) {
self.role = role
self.content = content
self.timestamp = Date()
self.toolName = toolName
self.isStreaming = isStreaming
self.isError = isError
}
var formattedTimestamp: String {
Self.formatter.string(from: timestamp)
}
private static let formatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "HH:mm:ss"
return f
}()
}
enum AgentStatus: Equatable {
case idle
case thinking
case executing(String)
case error(String)
var displayText: String {
switch self {
case .idle: return "Ready"
case .thinking: return "Thinking..."
case .executing(let tool): return "Running: \(tool)"
case .error(let msg): return "Error: \(msg)"
}
}
var color: Color {
switch self {
case .idle: return .green
case .thinking: return .yellow
case .executing: return .orange
case .error: return .red
}
}
var iconName: String {
switch self {
case .idle: return "checkmark.circle.fill"
case .thinking: return "brain"
case .executing: return "gearshape.2.fill"
case .error: return "exclamationmark.triangle.fill"
}
}
}
struct AgentToolInfo: Identifiable {
let id: String
let name: String
let description: String
let category: String
let isReadOnly: Bool
init(name: String, description: String, category: String = "General", isReadOnly: Bool = false) {
self.id = name
self.name = name
self.description = description
self.category = category
self.isReadOnly = isReadOnly
}
}
// MARK: - AI Model Configuration
enum AIProvider: String, CaseIterable, Identifiable, Codable {
case local = "NVIDIA"
case openAI = "OpenAI"
case anthropic = "Anthropic"
case ollama = "Ollama"
var id: String { rawValue }
var iconName: String {
switch self {
case .local: return "cpu"
case .openAI: return "globe"
case .anthropic: return "brain.head.profile"
case .ollama: return "server.rack"
}
}
var color: Color {
switch self {
case .local: return .green
case .openAI: return .cyan
case .anthropic: return .orange
case .ollama: return .purple
}
}
var models: [AIModel] {
switch self {
case .local:
return [
AIModel(id: "meta/llama-3.3-70b-instruct", name: "Llama 3.3 70B", provider: .local, contextWindow: 128_000, isLocal: false),
AIModel(id: "meta/llama-3.1-405b-instruct", name: "Llama 3.1 405B", provider: .local, contextWindow: 128_000, isLocal: false),
AIModel(id: "qwen/qwen2.5-coder-32b-instruct", name: "Qwen 2.5 Coder 32B", provider: .local, contextWindow: 32_000, isLocal: false),
]
case .openAI:
return [
AIModel(id: "gpt-4o", name: "GPT-4o", provider: .openAI, contextWindow: 128_000, isLocal: false),
AIModel(id: "gpt-4o-mini", name: "GPT-4o Mini", provider: .openAI, contextWindow: 128_000, isLocal: false),
AIModel(id: "o3-mini", name: "o3-mini", provider: .openAI, contextWindow: 128_000, isLocal: false),
]
case .anthropic:
return [
AIModel(id: "claude-sonnet-4-20250514", name: "Claude Sonnet 4", provider: .anthropic, contextWindow: 200_000, isLocal: false),
AIModel(id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku", provider: .anthropic, contextWindow: 200_000, isLocal: false),
]
case .ollama:
return [
AIModel(id: "codellama:13b", name: "CodeLlama 13B", provider: .ollama, contextWindow: 16_000, isLocal: true),
AIModel(id: "deepseek-coder:6.7b", name: "DeepSeek Coder", provider: .ollama, contextWindow: 16_000, isLocal: true),
AIModel(id: "qwen2.5-coder:7b", name: "Qwen 2.5 Coder", provider: .ollama, contextWindow: 32_000, isLocal: true),
]
}
}
/// The base API endpoint for this provider
var endpoint: String {
switch self {
case .local: return "https://integrate.api.nvidia.com/v1/chat/completions"
case .openAI: return "https://api.openai.com/v1/chat/completions"
case .anthropic: return "https://api.anthropic.com/v1/messages"
case .ollama: return "http://127.0.0.1:11434/v1/chat/completions"
}
}
}
struct AIModel: Identifiable, Hashable, Codable {
let id: String
let name: String
let provider: AIProvider
let contextWindow: Int
let isLocal: Bool
var displayName: String {
"\(name) (\(provider.rawValue))"
}
var contextLabel: String {
let k = contextWindow / 1000
return "\(k)K"
}
}
struct ChatMessage: Identifiable {
let id: UUID
let role: ChatRole
var content: String
let timestamp: Date
var toolResults: [ToolResult]
var isStreaming: Bool
var isError: Bool
var autoApplied: Bool
enum ChatRole: String {
case user, assistant, system, tool
var color: Color {
switch self {
case .user: return .blue
case .assistant: return .purple
case .system: return .cyan
case .tool: return .orange
}
}
var iconName: String {
switch self {
case .user: return "person.fill"
case .assistant: return "cpu"
case .system: return "gearshape.fill"
case .tool: return "wrench.fill"
}
}
}
struct ToolResult: Identifiable {
let id = UUID()
let toolName: String
let output: String
let isError: Bool
}
init(role: ChatRole, content: String, toolResults: [ToolResult] = [],
isStreaming: Bool = false, isError: Bool = false, autoApplied: Bool = false) {
self.id = UUID()
self.role = role
self.content = content
self.timestamp = Date()
self.toolResults = toolResults
self.isStreaming = isStreaming
self.isError = isError
self.autoApplied = autoApplied
}
var formattedTime: String {
Self.formatter.string(from: timestamp)
}
private static let formatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "HH:mm"
return f
}()
}
struct ChatSession: Identifiable {
let id = UUID()
var title: String
var messages: [ChatMessage]
var model: AIModel
let created: Date
init(title: String = "New Chat", model: AIModel) {
self.title = title
self.messages = []
self.model = model
self.created = Date()
}
var preview: String {
messages.first(where: { $0.role == .user })?.content.prefix(60).description ?? "Empty chat"
}
}
enum ChatMode: String, CaseIterable {
case chat = "Chat"
case agent = "Agent"
case edit = "Edit"
var iconName: String {
switch self {
case .chat: return "bubble.left.and.bubble.right"
case .agent: return "cpu"
case .edit: return "pencil.line"
}
}
var description: String {
switch self {
case .chat: return "Ask questions about your code"
case .agent: return "Agent can create, edit, run & build"
case .edit: return "Direct inline editing"
}
}
}