- Add Places/Restaurants content type with PlaceCard, PlaceDetail, PlaceGrid - Add WebsiteDetail and MagazineSectionDetail views for Context panel - Enhance MagazineGrid hero with background image and 3x taller header - Add mobile 3-tab layout (Chat, Content, Context) with detail navigation - Add /code command system: useCodeContext composable, ProjectGrid, FileTreeNode, CodeDetail for IDE-style code viewing across all three panels - Fix /code bubble and prompt index clicks to re-activate code mode - Fix updatePanelFromText overwriting code tab by skipping command messages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
207 lines
7.9 KiB
TypeScript
207 lines
7.9 KiB
TypeScript
import { ref, computed, shallowRef } from 'vue'
|
|
|
|
export interface ProjectInfo {
|
|
name: string
|
|
path: string
|
|
isGit: boolean
|
|
language?: string
|
|
}
|
|
|
|
export interface FileEntry {
|
|
name: string
|
|
path: string
|
|
isDirectory: boolean
|
|
children?: FileEntry[]
|
|
}
|
|
|
|
// Module-level singleton state
|
|
const codeMode = ref(false)
|
|
const activeProject = ref<ProjectInfo | null>(null)
|
|
const projectList = shallowRef<ProjectInfo[]>([])
|
|
const fileTree = shallowRef<FileEntry[]>([])
|
|
const activeFile = ref<string | null>(null)
|
|
const activeFileContent = ref<string>('')
|
|
const activeFileLanguage = ref<string>('plaintext')
|
|
|
|
// Demo projects path
|
|
const PROJECTS_ROOT = '/Users/dorian/Projects'
|
|
|
|
function detectLanguage(filename: string): string {
|
|
const ext = filename.split('.').pop()?.toLowerCase() ?? ''
|
|
const map: Record<string, string> = {
|
|
ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
|
|
vue: 'vue', svelte: 'svelte', py: 'python', rs: 'rust', go: 'go',
|
|
java: 'java', kt: 'kotlin', swift: 'swift', rb: 'ruby', php: 'php',
|
|
css: 'css', scss: 'scss', html: 'html', json: 'json', yaml: 'yaml',
|
|
yml: 'yaml', md: 'markdown', toml: 'toml', sh: 'shell', bash: 'shell',
|
|
sql: 'sql', graphql: 'graphql', dockerfile: 'dockerfile',
|
|
c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp', cs: 'csharp',
|
|
}
|
|
return map[ext] ?? 'plaintext'
|
|
}
|
|
|
|
function detectProjectLanguage(files: string[]): string {
|
|
if (files.includes('package.json')) return 'TypeScript/JavaScript'
|
|
if (files.includes('Cargo.toml')) return 'Rust'
|
|
if (files.includes('go.mod')) return 'Go'
|
|
if (files.includes('requirements.txt') || files.includes('setup.py') || files.includes('pyproject.toml')) return 'Python'
|
|
if (files.includes('pom.xml') || files.includes('build.gradle')) return 'Java'
|
|
if (files.includes('Package.swift')) return 'Swift'
|
|
if (files.includes('Gemfile')) return 'Ruby'
|
|
if (files.includes('composer.json')) return 'PHP'
|
|
if (files.some(f => f.endsWith('.csproj') || f.endsWith('.sln'))) return 'C#'
|
|
return 'Unknown'
|
|
}
|
|
|
|
export function useCodeContext() {
|
|
const isCodeMode = computed(() => codeMode.value)
|
|
const hasActiveProject = computed(() => activeProject.value !== null)
|
|
|
|
async function loadProjects(): Promise<void> {
|
|
// In dev/demo mode, scan the Projects folder
|
|
// This would be replaced by Archy integration later
|
|
try {
|
|
const response = await fetch(`/api/fs/list?path=${encodeURIComponent(PROJECTS_ROOT)}`)
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
projectList.value = data.projects ?? []
|
|
}
|
|
} catch {
|
|
// Fallback: use hardcoded list from build time
|
|
// In real app, this would come from local filesystem or Archy nodes
|
|
projectList.value = getDemoProjects()
|
|
}
|
|
}
|
|
|
|
function getDemoProjects(): ProjectInfo[] {
|
|
// Hardcoded demo list matching actual ~/Projects folder
|
|
return [
|
|
{ name: 'AIUI', path: `${PROJECTS_ROOT}/AIUI`, isGit: true, language: 'TypeScript/JavaScript' },
|
|
{ name: 'archy', path: `${PROJECTS_ROOT}/archy`, isGit: true, language: 'TypeScript/JavaScript' },
|
|
{ name: 'angor', path: `${PROJECTS_ROOT}/angor`, isGit: true, language: 'C#' },
|
|
{ name: 'angor-prototype', path: `${PROJECTS_ROOT}/angor-prototype`, isGit: true, language: 'TypeScript/JavaScript' },
|
|
{ name: 'archipelago', path: `${PROJECTS_ROOT}/archipelago`, isGit: true, language: 'Unknown' },
|
|
{ name: 'archipelago-foundation', path: `${PROJECTS_ROOT}/archipelago-foundation`, isGit: true, language: 'Unknown' },
|
|
{ name: 'blossom', path: `${PROJECTS_ROOT}/blossom`, isGit: true, language: 'TypeScript/JavaScript' },
|
|
{ name: 'fedimint', path: `${PROJECTS_ROOT}/fedimint`, isGit: true, language: 'Rust' },
|
|
{ name: 'Syntopy', path: `${PROJECTS_ROOT}/Syntopy`, isGit: true, language: 'Unknown' },
|
|
{ name: 'Syntropy-Institute', path: `${PROJECTS_ROOT}/Syntropy-Institute`, isGit: true, language: 'Unknown' },
|
|
{ name: 'LoRaBell', path: `${PROJECTS_ROOT}/LoRaBell`, isGit: true, language: 'Unknown' },
|
|
{ name: 'satoshi-services', path: `${PROJECTS_ROOT}/satoshi-services`, isGit: true, language: 'Unknown' },
|
|
{ name: 'Proux', path: `${PROJECTS_ROOT}/Proux`, isGit: true, language: 'Unknown' },
|
|
{ name: 'KYC', path: `${PROJECTS_ROOT}/KYC`, isGit: true, language: 'Unknown' },
|
|
{ name: 'k484', path: `${PROJECTS_ROOT}/k484`, isGit: true, language: 'Unknown' },
|
|
{ name: 'tbf', path: `${PROJECTS_ROOT}/tbf`, isGit: true, language: 'Unknown' },
|
|
{ name: 'Icon', path: `${PROJECTS_ROOT}/Icon`, isGit: false, language: 'Unknown' },
|
|
{ name: 'indeehub-frontend', path: `${PROJECTS_ROOT}/indeehub-frontend`, isGit: true, language: 'TypeScript/JavaScript' },
|
|
{ name: 'Indeedhub Prototype', path: `${PROJECTS_ROOT}/Indeedhub Prototype`, isGit: true, language: 'Unknown' },
|
|
{ name: '21', path: `${PROJECTS_ROOT}/21`, isGit: true, language: 'Unknown' },
|
|
]
|
|
}
|
|
|
|
function enterCodeMode(): void {
|
|
codeMode.value = true
|
|
loadProjects()
|
|
}
|
|
|
|
function exitCodeMode(): void {
|
|
codeMode.value = false
|
|
activeProject.value = null
|
|
activeFile.value = null
|
|
activeFileContent.value = ''
|
|
fileTree.value = []
|
|
}
|
|
|
|
function selectProject(project: ProjectInfo): void {
|
|
activeProject.value = project
|
|
loadFileTree(project.path)
|
|
}
|
|
|
|
async function loadFileTree(projectPath: string): Promise<void> {
|
|
try {
|
|
const response = await fetch(`/api/fs/tree?path=${encodeURIComponent(projectPath)}`)
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
fileTree.value = data.files ?? []
|
|
}
|
|
} catch {
|
|
// Demo fallback: generate a simple tree
|
|
fileTree.value = getDemoFileTree()
|
|
}
|
|
}
|
|
|
|
function getDemoFileTree(): FileEntry[] {
|
|
// Generic project structure for demo
|
|
return [
|
|
{ name: 'src', path: 'src', isDirectory: true, children: [
|
|
{ name: 'index.ts', path: 'src/index.ts', isDirectory: false },
|
|
{ name: 'app.ts', path: 'src/app.ts', isDirectory: false },
|
|
{ name: 'utils.ts', path: 'src/utils.ts', isDirectory: false },
|
|
]},
|
|
{ name: 'package.json', path: 'package.json', isDirectory: false },
|
|
{ name: 'tsconfig.json', path: 'tsconfig.json', isDirectory: false },
|
|
{ name: 'README.md', path: 'README.md', isDirectory: false },
|
|
]
|
|
}
|
|
|
|
async function openFile(filePath: string): Promise<void> {
|
|
activeFile.value = filePath
|
|
activeFileLanguage.value = detectLanguage(filePath)
|
|
|
|
try {
|
|
const fullPath = activeProject.value
|
|
? `${activeProject.value.path}/${filePath}`
|
|
: filePath
|
|
const response = await fetch(`/api/fs/read?path=${encodeURIComponent(fullPath)}`)
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
activeFileContent.value = data.content ?? ''
|
|
}
|
|
} catch {
|
|
// Demo fallback
|
|
activeFileContent.value = getDemoFileContent(filePath)
|
|
}
|
|
}
|
|
|
|
function getDemoFileContent(filePath: string): string {
|
|
const name = filePath.split('/').pop() ?? filePath
|
|
if (name === 'package.json') {
|
|
return JSON.stringify({
|
|
name: activeProject.value?.name?.toLowerCase() ?? 'project',
|
|
version: '1.0.0',
|
|
type: 'module',
|
|
scripts: { dev: 'vite', build: 'vite build', test: 'vitest' },
|
|
dependencies: {},
|
|
}, null, 2)
|
|
}
|
|
if (name === 'README.md') {
|
|
return `# ${activeProject.value?.name ?? 'Project'}\n\nA project in the AIUI ecosystem.\n`
|
|
}
|
|
if (name.endsWith('.ts') || name.endsWith('.js')) {
|
|
return `// ${name}\n// ${activeProject.value?.name ?? 'Project'}\n\nexport function main() {\n console.log('Hello from ${name}')\n}\n`
|
|
}
|
|
return `// ${name}\n`
|
|
}
|
|
|
|
return {
|
|
// State
|
|
codeMode,
|
|
isCodeMode,
|
|
activeProject,
|
|
hasActiveProject,
|
|
projectList,
|
|
fileTree,
|
|
activeFile,
|
|
activeFileContent,
|
|
activeFileLanguage,
|
|
|
|
// Actions
|
|
enterCodeMode,
|
|
exitCodeMode,
|
|
selectProject,
|
|
openFile,
|
|
loadProjects,
|
|
detectLanguage,
|
|
}
|
|
}
|