Implement a comprehensive artifacts system that allows users to create, edit, and manage persistent documents alongside conversations, inspired by Claude's artifacts feature. Key Features: - AI model integration with system prompts teaching artifact usage - Inline preview cards in chat messages with collapsible previews - On-demand side panel overlay (replaces split view) - Preview/Code toggle for rendered markdown vs raw content - Artifacts sidebar for managing multiple artifacts per thread - Monaco editor integration for code artifacts - Autosave with debounced writes and conflict detection - Diff preview system for proposed updates - Keyboard shortcuts and quick switcher - Export/import functionality Backend (Rust): - New artifacts module in src-tauri/src/core/artifacts/ - 12 Tauri commands for CRUD, proposals, and export/import - Atomic file writes with SHA256 hashing - Sharded history storage with pruning policy - Path traversal validation and UTF-8 enforcement Frontend (TypeScript/React): - ArtifactSidePanel: Claude-style overlay panel from right - InlineArtifactCard: Preview cards embedded in chat - ArtifactsSidebar: Floating list for artifact switching - Enhanced ArtifactActionMessage: Parses AI metadata from content - useArtifacts: Zustand store with autosave and conflict resolution Types: - Extended ThreadMessage.metadata with ArtifactAction - ProposedContentRef supports inline and temp storage - MIME-like content_type values for extensibility Platform: - Fixed platform detection to check window.__TAURI__ - Web service stubs for browser mode compatibility - Updated assistant system prompts in both extension and web-app This implements the complete workflow studied from Claude: 1. AI only creates artifacts when explicitly requested 2. Inline cards appear in chat with preview buttons 3. Side panel opens on demand, not automatic split 4. Users can toggle Preview/Code views and edit content 5. Autosave and version management prevent data loss
98 lines
2.6 KiB
TypeScript
98 lines
2.6 KiB
TypeScript
/**
|
|
* Web implementation of ArtifactsService (minimal stub)
|
|
*
|
|
* This is a minimal implementation for browser mode.
|
|
* Most functionality requires Tauri backend.
|
|
*/
|
|
|
|
import type {
|
|
Artifact,
|
|
ArtifactIndex,
|
|
ArtifactCreate,
|
|
DiffPreview,
|
|
ImportResult,
|
|
} from '@janhq/core'
|
|
import type { ArtifactsService } from './types'
|
|
|
|
export class WebArtifactsService implements ArtifactsService {
|
|
private notImplemented(method: string): never {
|
|
throw new Error(`${method} is not implemented in web mode. Use Tauri app for full artifacts support.`)
|
|
}
|
|
|
|
async listArtifacts(_threadId: string): Promise<ArtifactIndex> {
|
|
return {
|
|
schema_version: 1,
|
|
artifacts: [],
|
|
active_artifact_id: null,
|
|
history_keep: {
|
|
max_entries: 50,
|
|
max_days: 30,
|
|
},
|
|
}
|
|
}
|
|
|
|
async createArtifact(_threadId: string, _data: ArtifactCreate): Promise<Artifact> {
|
|
return this.notImplemented('createArtifact')
|
|
}
|
|
|
|
async getArtifactContent(_threadId: string, _artifactId: string): Promise<string> {
|
|
return this.notImplemented('getArtifactContent')
|
|
}
|
|
|
|
async updateArtifact(
|
|
_threadId: string,
|
|
_artifactId: string,
|
|
_content: string,
|
|
_version: number,
|
|
_hash: string
|
|
): Promise<Artifact> {
|
|
return this.notImplemented('updateArtifact')
|
|
}
|
|
|
|
async deleteArtifact(_threadId: string, _artifactId: string): Promise<void> {
|
|
return this.notImplemented('deleteArtifact')
|
|
}
|
|
|
|
async renameArtifact(_threadId: string, _artifactId: string, _newName: string): Promise<Artifact> {
|
|
return this.notImplemented('renameArtifact')
|
|
}
|
|
|
|
async setActiveArtifact(_threadId: string, _artifactId: string | null): Promise<void> {
|
|
// No-op in web mode
|
|
return Promise.resolve()
|
|
}
|
|
|
|
async proposeUpdate(_threadId: string, _artifactId: string, _content: string): Promise<DiffPreview> {
|
|
return {
|
|
proposal_id: 'stub',
|
|
artifact_id: _artifactId,
|
|
current_version: 1,
|
|
current_hash: '',
|
|
proposed_hash: '',
|
|
hunks: [],
|
|
full_diff: '',
|
|
}
|
|
}
|
|
|
|
async applyProposal(
|
|
_threadId: string,
|
|
_artifactId: string,
|
|
_proposalId: string,
|
|
_selectedHunks?: number[]
|
|
): Promise<Artifact> {
|
|
return this.notImplemented('applyProposal')
|
|
}
|
|
|
|
async discardProposal(_threadId: string, _artifactId: string, _proposalId: string): Promise<void> {
|
|
return Promise.resolve()
|
|
}
|
|
|
|
async exportArtifacts(_threadId: string, _outputPath: string): Promise<void> {
|
|
return Promise.resolve()
|
|
}
|
|
|
|
async importArtifacts(_threadId: string, _archivePath: string): Promise<ImportResult> {
|
|
return this.notImplemented('importArtifacts')
|
|
}
|
|
}
|