Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ jobs:
- name: Run typecheck
run: npm run typecheck

eslint:
name: ESLint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm

- name: Install dependencies
run: npm ci

- name: Run ESLint
run: npm run lint

build:
name: Build
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
npm run lint:md
npx lint-staged
27 changes: 22 additions & 5 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@
openBuiltin('chat');

if (import.meta.client) {
(window as any).__dominicReset = () => {

Check warning on line 115 in app/app.vue

View workflow job for this annotation

GitHub Actions / ESLint

Unexpected any. Specify a different type
settings.resetOs();
window.location.reload();
};
(window as any).__dominic = {

Check warning on line 119 in app/app.vue

View workflow job for this annotation

GitHub Actions / ESLint

Unexpected any. Specify a different type
reset: (window as any).__dominicReset,

Check warning on line 120 in app/app.vue

View workflow job for this annotation

GitHub Actions / ESLint

Unexpected any. Specify a different type
installApp,
updateApp,
openApp,
Expand Down Expand Up @@ -155,13 +155,25 @@
<div class="relative h-screen w-screen overflow-hidden bg-gradient-to-br from-slate-950 via-indigo-950 to-slate-900 text-slate-100 max-md:overflow-y-auto max-md:overscroll-contain max-md:pb-16">
<div class="pointer-events-none absolute inset-0 flex items-center justify-center">
<div class="text-center opacity-25">
<h1 class="text-5xl font-bold tracking-tight max-md:text-4xl">DOMinic</h1>
<p class="mt-3 text-slate-400 max-md:px-6">An AI agent is the primary app author.</p>
<h1 class="text-5xl font-bold tracking-tight max-md:text-4xl">
DOMinic
</h1>
<p class="mt-3 text-slate-400 max-md:px-6">
An AI agent is the primary app author.
</p>
</div>
</div>

<WindowFrame v-for="win in os.windows" :key="win.id" :win="win">
<ChatWindow v-if="win.appId === 'chat'" :ref="setChat" :options="{ getProviderConfig: () => settings, getInstalledApps: () => apps.installed, getAppSource, onInstallApp: installApp, onUpdateApp: updateApp, onOpenWindow: openApp }" />
<WindowFrame
v-for="win in os.windows"
:key="win.id"
:win="win"
>
<ChatWindow
v-if="win.appId === 'chat'"
:ref="setChat"
:options="{ getProviderConfig: () => settings, getInstalledApps: () => apps.installed, getAppSource, onInstallApp: installApp, onUpdateApp: updateApp, onOpenWindow: openApp }"
/>
<SettingsApp v-else-if="win.appId === 'settings'" />
<pre
v-else-if="win.appId?.startsWith('source:')"
Expand All @@ -175,7 +187,12 @@
:source-code="readFile(getApp(win.appId)?.entry || '') || ''"
@ask-fix="askFix"
/>
<p v-else class="text-sm text-slate-400">{{ win.title }}</p>
<p
v-else
class="text-sm text-slate-400"
>
{{ win.title }}
</p>
</WindowFrame>

<Taskbar
Expand Down
27 changes: 25 additions & 2 deletions app/features/apps/runner/DynamicAppRunner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,31 @@
class="flex-1 flex flex-col p-6 overflow-y-auto bg-rose-950/20 text-rose-200"
>
<div class="flex items-center space-x-2 text-rose-400 mb-3">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-5 h-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle
cx="12"
cy="12"
r="10"
/><line
x1="12"
y1="8"
x2="12"
y2="12"
/><line
x1="12"
y1="16"
x2="12.01"
y2="16"
/>
</svg>
<span class="font-semibold text-sm">{{ isRuntimeError ? 'Application Runtime Error' : 'Component Compilation Error' }}</span>
</div>
Expand Down Expand Up @@ -71,7 +94,7 @@
const isLoading = ref(true)
const error = ref<string | null>(null)
const isRuntimeError = ref(false)
const compiledComponent = shallowRef<any>(null)

Check warning on line 97 in app/features/apps/runner/DynamicAppRunner.vue

View workflow job for this annotation

GitHub Actions / ESLint

Unexpected any. Specify a different type

async function loadComponent() {
if (!props.sourceCode?.trim()) {
Expand All @@ -90,7 +113,7 @@
appId: props.appId
})
compiledComponent.value = comp
} catch (err: any) {

Check warning on line 116 in app/features/apps/runner/DynamicAppRunner.vue

View workflow job for this annotation

GitHub Actions / ESLint

Unexpected any. Specify a different type
console.error(`[DOMinic Engine] Compilation error in "${props.appId}":`, err)
error.value = err?.message || String(err)
} finally {
Expand Down
48 changes: 37 additions & 11 deletions app/features/chat/components/ChatWindow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
<!-- Sub-header / status bar -->
<div class="flex items-center justify-between px-4 py-2 border-b border-slate-800/80 bg-slate-900/60 text-xs text-slate-400">
<div class="flex items-center space-x-2">
<span class="inline-block w-2 h-2 rounded-full" :class="isStreaming ? 'bg-amber-400 animate-pulse' : 'bg-emerald-400'" />
<span
class="inline-block w-2 h-2 rounded-full"
:class="isStreaming ? 'bg-amber-400 animate-pulse' : 'bg-emerald-400'"
/>
<span>{{ isStreaming ? 'Synthesizing...' : 'DOMinic Kernel Ready' }}</span>
</div>
<div class="flex items-center space-x-3">
Expand Down Expand Up @@ -56,8 +59,14 @@
class="flex items-center justify-between p-2.5 rounded-lg bg-slate-950/70 border border-slate-800 text-xs"
>
<div class="flex items-center space-x-2">
<span v-if="tool.state === 'call'" class="animate-spin text-blue-400">⚙️</span>
<span v-else class="text-emerald-400">✅</span>
<span
v-if="tool.state === 'call'"
class="animate-spin text-blue-400"
>⚙️</span>
<span
v-else
class="text-emerald-400"
>✅</span>

<div class="flex flex-col">
<span class="font-medium text-slate-200">
Expand Down Expand Up @@ -85,16 +94,31 @@
</div>

<!-- Streaming thinking indicator -->
<div v-if="isStreaming" class="flex items-center space-x-2 text-xs text-slate-500 italic">
<span class="inline-block w-1.5 h-1.5 rounded-full bg-slate-500 animate-bounce" style="animation-delay: 0ms" />
<span class="inline-block w-1.5 h-1.5 rounded-full bg-slate-500 animate-bounce" style="animation-delay: 150ms" />
<span class="inline-block w-1.5 h-1.5 rounded-full bg-slate-500 animate-bounce" style="animation-delay: 300ms" />
<div
v-if="isStreaming"
class="flex items-center space-x-2 text-xs text-slate-500 italic"
>
<span
class="inline-block w-1.5 h-1.5 rounded-full bg-slate-500 animate-bounce"
style="animation-delay: 0ms"
/>
<span
class="inline-block w-1.5 h-1.5 rounded-full bg-slate-500 animate-bounce"
style="animation-delay: 150ms"
/>
<span
class="inline-block w-1.5 h-1.5 rounded-full bg-slate-500 animate-bounce"
style="animation-delay: 300ms"
/>
<span>Authoring code...</span>
</div>
</div>

<!-- Quick Demo Suggestion Chips -->
<div v-if="messages.length <= 2" class="px-4 pb-2 flex flex-wrap gap-1.5">
<div
v-if="messages.length <= 2"
class="px-4 pb-2 flex flex-wrap gap-1.5"
>
<button
v-for="suggestion in suggestions"
:key="suggestion.label"
Expand All @@ -107,14 +131,17 @@

<!-- Input Footer -->
<div class="p-3 border-t border-slate-800/80 bg-slate-900/40">
<form class="flex items-center space-x-2" @submit.prevent="handleSend">
<form
class="flex items-center space-x-2"
@submit.prevent="handleSend"
>
<input
v-model="input"
type="text"
placeholder="Ask DOMinic to build an app (e.g. 'Build a pomodoro timer')..."
:disabled="isStreaming"
class="flex-1 bg-slate-950 border border-slate-800 rounded-xl px-3.5 py-2 text-sm text-slate-100 placeholder-slate-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 disabled:opacity-50"
/>
>

<button
type="submit"
Expand Down Expand Up @@ -142,7 +169,6 @@ const {
messages,
input,
isStreaming,
error,
sendMessage,
clearHistory
} = useAgentChat(props.options)
Expand Down
2 changes: 2 additions & 0 deletions app/features/os/components/Taskbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { Code2, Trash2 } from "lucide-vue-next";
import { useOsStore } from "../stores/os";

defineOptions({ name: "OsTaskbar" });

const store = useOsStore();
const props = defineProps<{
apps: { id: string; title: string; icon: string }[];
Expand Down
5 changes: 4 additions & 1 deletion app/features/os/components/WindowFrame.vue
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ watch(dragging, (active) => {
@pointerup="onPointerUp"
>
<span class="truncate text-xs font-medium text-slate-200">{{ win.title }}</span>
<span class="flex items-center gap-1" @pointerdown.stop>
<span
class="flex items-center gap-1"
@pointerdown.stop
>
<button
class="flex h-5 w-5 items-center justify-center rounded text-slate-400 hover:bg-white/10 hover:text-slate-100"
aria-label="Minimize"
Expand Down
35 changes: 26 additions & 9 deletions app/features/settings/components/SettingsApp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
<div class="h-full w-full flex flex-col bg-slate-950 text-slate-100 p-6 overflow-y-auto space-y-6 select-text text-sm">
<!-- Header -->
<div class="border-b border-slate-800/80 pb-4">
<h2 class="text-lg font-semibold text-white">System Settings</h2>
<h2 class="text-lg font-semibold text-white">
System Settings
</h2>
<p class="text-xs text-slate-400 mt-1">
Configure your frontier LLM provider credentials and manage OS persistence.
</p>
Expand Down Expand Up @@ -42,7 +44,7 @@
:placeholder="selectedProvider === 'custom' ? 'e.g. openai/gpt-4o-mini or llama3.2' : 'gpt-5, claude-sonnet-5, gemini-2.5-pro, or deepseek-chat'"
class="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-xs text-white placeholder:text-slate-500 transition focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
@input="baseUrlError = ''"
/>
>
</div>

<!-- API Key Input -->
Expand All @@ -64,7 +66,7 @@
autocomplete="off"
spellcheck="false"
class="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 font-mono text-xs text-white placeholder:text-slate-500 transition focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
/>
>
<span class="text-[11px] text-slate-500">
Your key never leaves your browser at rest. It is passed ephemerally per request.
</span>
Expand All @@ -73,17 +75,26 @@
<!-- Custom Base URL -->
<div class="space-y-1.5">
<label class="text-xs text-slate-300 font-medium">
Custom Base URL <span v-if="selectedProvider === 'custom'" class="text-rose-400">(required)</span>
<span v-else class="text-slate-500">(Optional)</span>
Custom Base URL <span
v-if="selectedProvider === 'custom'"
class="text-rose-400"
>(required)</span>
<span
v-else
class="text-slate-500"
>(Optional)</span>
</label>
<input
v-model="baseUrlInput"
type="text"
:placeholder="selectedProvider === 'custom' ? 'https://openrouter.ai/api/v1' : 'https://api.openai.com/v1 or custom proxy'"
class="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 font-mono text-xs text-white placeholder:text-slate-500 transition focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
@input="baseUrlError = ''"
/>
<span v-if="selectedProvider === 'custom'" class="text-[11px] text-slate-500">
>
<span
v-if="selectedProvider === 'custom'"
class="text-[11px] text-slate-500"
>
Any OpenAI-compatible endpoint, e.g. https://openrouter.ai/api/v1, http://localhost:11434/v1
</span>
</div>
Expand All @@ -98,11 +109,17 @@
Save Credentials
</button>

<span v-if="savedNotice" class="text-xs text-emerald-400 animate-fade-in">
<span
v-if="savedNotice"
class="text-xs text-emerald-400 animate-fade-in"
>
✓ Saved to local storage
</span>

<span v-if="baseUrlError" class="text-xs text-rose-400">
<span
v-if="baseUrlError"
class="text-xs text-rose-400"
>
{{ baseUrlError }}
</span>
</div>
Expand Down
1 change: 0 additions & 1 deletion app/features/shared/vfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export function deleteFile(path: string): void {
export function listFiles(dir: string = ''): { path: string; size: number }[] {
if (!isBrowser()) return []
const normalizedDir = normalizePath(dir)
const prefix = `${VFS_PREFIX}${normalizedDir}`
const results: { path: string; size: number }[] = []

for (let i = 0; i < window.localStorage.length; i++) {
Expand Down
Loading
Loading