feat: initialize AIUI monorepo with project rules and core types
Foundation for the next-generation AI content surface UI: - 16 Cursor rules files covering philosophy, Vue conventions, design system, content surfaces, plugin system, AI integration, renderers, security, Bitcoin-only policy, dev/prod modes, accessibility, performance, animation, mobile UX, and git workflow - pnpm workspaces + Turborepo monorepo (@aiui/core, @aiui/app) - Vue 3 + Vite + TypeScript + Tailwind CSS 4 - Core type system: plugins, renderers, messages, content blocks - Plugin registry with renderer registration - 50 mock film fixtures with search/filter utilities - App shell with chat page layout - Environment config templates Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@aiui/core",
|
||||
"version": "0.1.0",
|
||||
"description": "AIUI core component library - rich AI content surface renderers",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./components/*": "./src/components/*",
|
||||
"./composables/*": "./src/composables/*",
|
||||
"./plugins/*": "./src/plugins/*",
|
||||
"./types/*": "./src/types/*",
|
||||
"./styles/*": "./src/styles/*"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"test": "vitest run",
|
||||
"lint": "eslint src/",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "latest",
|
||||
"vite": "latest",
|
||||
"vue": "latest",
|
||||
"vue-tsc": "latest",
|
||||
"vitest": "latest",
|
||||
"eslint": "latest",
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<object, object, unknown>
|
||||
export default component
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './types/plugin'
|
||||
export * from './types/renderer'
|
||||
export * from './types/message'
|
||||
export * from './types/content'
|
||||
export * from './plugins/registry'
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ref, readonly } from 'vue'
|
||||
import type { AIUIPlugin, PluginType } from '../types/plugin'
|
||||
import type { RendererDefinition } from '../types/renderer'
|
||||
|
||||
const plugins = ref<Map<string, AIUIPlugin>>(new Map())
|
||||
const renderers = ref<Map<string, RendererDefinition>>(new Map())
|
||||
|
||||
export function registerPlugin(plugin: AIUIPlugin): void {
|
||||
if (plugins.value.has(plugin.id)) {
|
||||
console.warn(`Plugin "${plugin.id}" is already registered. Skipping.`)
|
||||
return
|
||||
}
|
||||
plugins.value.set(plugin.id, plugin)
|
||||
}
|
||||
|
||||
export function unregisterPlugin(pluginId: string): void {
|
||||
plugins.value.delete(pluginId)
|
||||
}
|
||||
|
||||
export function getPlugin<T extends AIUIPlugin>(pluginId: string): T | undefined {
|
||||
return plugins.value.get(pluginId) as T | undefined
|
||||
}
|
||||
|
||||
export function getPluginsByType<T extends AIUIPlugin>(type: PluginType): T[] {
|
||||
return Array.from(plugins.value.values()).filter(
|
||||
(p) => p.type === type
|
||||
) as T[]
|
||||
}
|
||||
|
||||
export function registerRenderer(renderer: RendererDefinition): void {
|
||||
if (renderers.value.has(renderer.id)) {
|
||||
console.warn(`Renderer "${renderer.id}" is already registered. Skipping.`)
|
||||
return
|
||||
}
|
||||
renderers.value.set(renderer.id, renderer)
|
||||
}
|
||||
|
||||
export function getRendererForContentType(
|
||||
contentType: string
|
||||
): RendererDefinition | undefined {
|
||||
return Array.from(renderers.value.values()).find(
|
||||
(r) => r.contentType === contentType
|
||||
)
|
||||
}
|
||||
|
||||
export function getAllRenderers(): RendererDefinition[] {
|
||||
return Array.from(renderers.value.values())
|
||||
}
|
||||
|
||||
export const pluginRegistry = readonly(plugins)
|
||||
export const rendererRegistry = readonly(renderers)
|
||||
@@ -0,0 +1,35 @@
|
||||
export interface ContentBlock {
|
||||
contentType: string
|
||||
data: Record<string, unknown>
|
||||
title?: string
|
||||
}
|
||||
|
||||
export interface Film {
|
||||
id: string
|
||||
title: string
|
||||
year: number
|
||||
posterUrl: string
|
||||
backdropUrl?: string
|
||||
synopsis: string
|
||||
genres: string[]
|
||||
rating: number
|
||||
runtime: number
|
||||
director: string
|
||||
cast: string[]
|
||||
trailerUrl?: string
|
||||
sources: FilmSource[]
|
||||
}
|
||||
|
||||
export interface FilmSource {
|
||||
type: 'plex' | 'nextcloud' | 'youtube' | 'free-web'
|
||||
name: string
|
||||
url: string
|
||||
quality?: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
export interface FilmRendererData {
|
||||
films: Film[]
|
||||
query?: string
|
||||
totalResults?: number
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { ContentBlock } from './content'
|
||||
|
||||
export interface Message {
|
||||
id: string
|
||||
role: 'user' | 'assistant' | 'system'
|
||||
content: string
|
||||
contentBlocks?: ContentBlock[]
|
||||
timestamp: number
|
||||
model?: string
|
||||
usage?: { promptTokens: number; completionTokens: number }
|
||||
replyTo?: string
|
||||
reactions?: Reaction[]
|
||||
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error'
|
||||
}
|
||||
|
||||
export interface Reaction {
|
||||
emoji: string
|
||||
userId: string
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: string
|
||||
title: string
|
||||
messages: Message[]
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
model?: string
|
||||
systemPrompt?: string
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
export type PluginType =
|
||||
| 'ai-provider'
|
||||
| 'media-source'
|
||||
| 'messaging'
|
||||
| 'storage'
|
||||
| 'renderer'
|
||||
| 'file-handler'
|
||||
| 'crypto'
|
||||
| 'search'
|
||||
| 'auth'
|
||||
| 'wallet'
|
||||
| 'social-embed'
|
||||
| 'mcp'
|
||||
| 'media'
|
||||
|
||||
export interface PluginContext {
|
||||
settings: PluginSettingsStore
|
||||
events: PluginEventBus
|
||||
logger: PluginLogger
|
||||
}
|
||||
|
||||
export interface PluginSettingsStore {
|
||||
get<T>(key: string): T | undefined
|
||||
set<T>(key: string, value: T): void
|
||||
}
|
||||
|
||||
export interface PluginEventBus {
|
||||
emit(event: string, payload?: unknown): void
|
||||
on(event: string, handler: (payload?: unknown) => void): () => void
|
||||
}
|
||||
|
||||
export interface PluginLogger {
|
||||
info(message: string, ...args: unknown[]): void
|
||||
warn(message: string, ...args: unknown[]): void
|
||||
error(message: string, ...args: unknown[]): void
|
||||
}
|
||||
|
||||
export interface AIUIPlugin {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
type: PluginType
|
||||
description?: string
|
||||
icon?: string
|
||||
init(context: PluginContext): Promise<void>
|
||||
destroy(): Promise<void>
|
||||
isAvailable(): Promise<boolean>
|
||||
}
|
||||
|
||||
export interface AIProviderAdapter extends AIUIPlugin {
|
||||
type: 'ai-provider'
|
||||
chat(messages: ChatMessage[], options: ChatOptions): AsyncIterable<ChatChunk>
|
||||
models(): Promise<AIModel[]>
|
||||
supportsStreaming: boolean
|
||||
supportsVision: boolean
|
||||
supportsTools: boolean
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'system' | 'user' | 'assistant' | 'tool'
|
||||
content: string | ContentPart[]
|
||||
toolCalls?: ToolCall[]
|
||||
toolCallId?: string
|
||||
}
|
||||
|
||||
export interface ContentPart {
|
||||
type: 'text' | 'image_url'
|
||||
text?: string
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
export interface ChatOptions {
|
||||
model: string
|
||||
temperature?: number
|
||||
maxTokens?: number
|
||||
tools?: ToolDefinition[]
|
||||
stream?: boolean
|
||||
}
|
||||
|
||||
export interface ChatChunk {
|
||||
type: 'text' | 'tool_call' | 'done' | 'error'
|
||||
text?: string
|
||||
toolCall?: ToolCall
|
||||
error?: string
|
||||
usage?: { promptTokens: number; completionTokens: number }
|
||||
}
|
||||
|
||||
export interface ToolCall {
|
||||
id: string
|
||||
name: string
|
||||
arguments: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ToolDefinition {
|
||||
name: string
|
||||
description: string
|
||||
parameters: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface ToolResult {
|
||||
toolCallId: string
|
||||
content: string
|
||||
isError: boolean
|
||||
}
|
||||
|
||||
export interface AIModel {
|
||||
id: string
|
||||
name: string
|
||||
provider: string
|
||||
supportsVision: boolean
|
||||
supportsTools: boolean
|
||||
contextWindow: number
|
||||
}
|
||||
|
||||
export interface MediaSourcePlugin extends AIUIPlugin {
|
||||
type: 'media-source'
|
||||
search(query: string): Promise<MediaItem[]>
|
||||
getLibrary(filters?: Record<string, unknown>): Promise<MediaItem[]>
|
||||
getPlayUrl(itemId: string): Promise<string>
|
||||
}
|
||||
|
||||
export interface MediaItem {
|
||||
id: string
|
||||
title: string
|
||||
type: 'film' | 'tv' | 'music' | 'podcast' | 'audiobook'
|
||||
posterUrl?: string
|
||||
year?: number
|
||||
rating?: number
|
||||
source: string
|
||||
sourceIcon?: string
|
||||
}
|
||||
|
||||
export interface WalletPlugin extends AIUIPlugin {
|
||||
type: 'wallet'
|
||||
supports: PaymentMethod[]
|
||||
isInstalled(): Promise<boolean>
|
||||
getPayUri(request: PaymentRequest): string
|
||||
openWallet(request: PaymentRequest): Promise<void>
|
||||
}
|
||||
|
||||
export type PaymentMethod = 'lightning' | 'onchain' | 'cashu' | 'fedimint'
|
||||
|
||||
export interface PaymentRequest {
|
||||
type: PaymentMethod
|
||||
invoice?: string
|
||||
address?: string
|
||||
amount?: number
|
||||
memo?: string
|
||||
lnurl?: string
|
||||
cashuToken?: string
|
||||
mintUrl?: string
|
||||
}
|
||||
|
||||
export interface SocialEmbedPlugin extends AIUIPlugin {
|
||||
type: 'social-embed'
|
||||
platform: 'x' | 'nostr' | 'mastodon' | 'bluesky'
|
||||
fetchPost(url: string): Promise<SocialPost>
|
||||
fetchThread(url: string): Promise<SocialPost[]>
|
||||
}
|
||||
|
||||
export interface SocialPost {
|
||||
id: string
|
||||
author: { name: string; handle: string; avatarUrl: string }
|
||||
content: string
|
||||
media?: { type: 'image' | 'video'; url: string }[]
|
||||
metrics?: { likes: number; reposts: number; replies: number }
|
||||
timestamp: string
|
||||
url: string
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Component } from 'vue'
|
||||
|
||||
export type SurfaceType =
|
||||
| 'chat-preview'
|
||||
| 'chat-play'
|
||||
| 'panel-preview'
|
||||
| 'panel-play'
|
||||
| 'panel-edit'
|
||||
|
||||
export interface RendererDefinition {
|
||||
id: string
|
||||
name: string
|
||||
contentType: string
|
||||
surfaces: SurfaceType[]
|
||||
chatPreview?: Component | (() => Promise<Component>)
|
||||
chatPlay?: Component | (() => Promise<Component>)
|
||||
panelPreview?: Component | (() => Promise<Component>)
|
||||
panelPlay?: Component | (() => Promise<Component>)
|
||||
panelEdit?: Component | (() => Promise<Component>)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.vue"]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
name: 'AIUICore',
|
||||
formats: ['es'],
|
||||
fileName: 'aiui-core',
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['vue'],
|
||||
output: {
|
||||
globals: { vue: 'Vue' },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user