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
2 changes: 1 addition & 1 deletion clients/agent-runtime/firmware/corvus-esp32-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,4 @@ MIT - See root `LICENSE`

- [Slint ESP32 Documentation](https://slint.dev/esp32)
- [ESP-IDF Rust Book](https://docs.espressif.com/projects/rust/book/)
- [Corvus Hardware Design](../../../../docs/en/guides/hardware-peripherals-design.md)
- [Corvus Hardware Design](../../../web/apps/docs/src/content/docs/en/guides/hardware-peripherals-design.md)
2 changes: 1 addition & 1 deletion clients/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ clients/web/

### dashboard

- Estado: placeholder
- Framework: Vue 3 + Vite + Tailwind + shadcn-vue style components
- Puerto por defecto: 4323

## 🛠️ Comandos
Expand Down
25 changes: 14 additions & 11 deletions clients/web/apps/dashboard/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
# Dashboard

Corvus web dashboard - coming soon.
Corvus web dashboard implemented with Vue 3 + Vite and shadcn-vue style components.

## Development Plan
## Features

- **Framework**: Vue/React (TBD)
- **Features**:
- Authentication
- Agent management
- Analytics dashboard
- Settings panel
- Real-time updates
- Basic chat workspace aligned with the Corvus system design:
- Header with model name
- Chat panel with user/assistant bubbles
- Gateway config panel (base URL, pairing code, bearer token, webhook secret)
- Message composer with send action
- Local state only (mock assistant responses for now)
- Tailwind CSS v4 styling with reusable shadcn-vue-inspired UI primitives (`Button`, `Input`)

## Getting Started
## Run

```bash
# From web root
# From clients/web
pnpm install
pnpm dev:dashboard
```

Dashboard runs on <http://localhost:4323>.
15 changes: 15 additions & 0 deletions clients/web/apps/dashboard/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://www.shadcn-vue.com/schema.json",
"style": "default",
"typescript": true,
"tailwind": {
"config": "",
"css": "src/style.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
12 changes: 12 additions & 0 deletions clients/web/apps/dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Corvus Dashboard</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
33 changes: 25 additions & 8 deletions clients/web/apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,30 @@
"description": "Corvus Web Dashboard",
"type": "module",
"scripts": {
"dev": "echo 'Dashboard app not yet implemented' && exit 1",
"build": "echo 'Dashboard app not yet implemented' && exit 1",
"preview": "echo 'Dashboard app not yet implemented' && exit 1",
"format": "echo 'No files to format'",
"check": "echo 'No files to check'",
"clean": "rm -rf dist"
"dev": "vite --port 4323",
"build": "vue-tsc -b && vite build",
"preview": "vite preview --port 4323",
"format": "biome format --write src package.json components.json tsconfig*.json vite.config.ts index.html postcss.config.js",
"check": "biome check src package.json components.json tsconfig*.json vite.config.ts index.html postcss.config.js"
},
"dependencies": {},
"devDependencies": {}
"dependencies": {
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"tailwind-merge": "^3.3.1",
"vue": "^3.5.22"
Comment on lines +14 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Commit lockfile entries for dashboard dependencies

This change adds new dependencies to the dashboard manifest, but clients/web/pnpm-lock.yaml was not updated (importers.apps/dashboard remains {}), so installs that enforce lockfile consistency will fail with an outdated lockfile error. In this repository that directly impacts both Gradle-driven web builds (clients/web/build.gradle.kts uses install --frozen-lockfile when the lockfile exists) and CI workflows that run frozen installs, blocking the new dashboard package from being built.

Useful? React with 👍 / 👎.

},
"devDependencies": {
"@biomejs/biome": "2.3.15",
"@tailwindcss/postcss": "^4.1.16",
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
Comment on lines +20 to +27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove autoprefixer — it's unnecessary with Tailwind CSS v4.

Tailwind v4 has built-in Lightning CSS integration that handles vendor prefixes automatically. The autoprefixer dependency on Line 26 is unused overhead.

♻️ Proposed fix
   "devDependencies": {
     "@biomejs/biome": "2.3.15",
     "@tailwindcss/postcss": "^4.1.16",
     "@types/node": "^24.10.1",
     "@vitejs/plugin-vue": "^6.0.1",
     "@vue/tsconfig": "^0.8.1",
-    "autoprefixer": "^10.4.21",
     "postcss": "^8.5.6",
     "tailwindcss": "^4.1.16",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"devDependencies": {
"@biomejs/biome": "2.3.15",
"@tailwindcss/postcss": "^4.1.16",
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"autoprefixer": "^10.4.21",
"postcss": "^8.5.6",
"devDependencies": {
"@biomejs/biome": "2.3.15",
"@tailwindcss/postcss": "^4.1.16",
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"postcss": "^8.5.6",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/package.json` around lines 20 - 27, Remove the
unnecessary autprefixer devDependency from the devDependencies block in
package.json: delete the "autoprefixer" entry (the string "autoprefixer") and
ensure the JSON remains valid (commas adjusted) since Tailwind CSS v4 handles
vendor prefixes via Lightning CSS; keep other devDependencies like "postcss" and
"@tailwindcss/postcss" untouched.

"tailwindcss": "^4.1.16",
"typescript": "^5.9.3",
"vite": "^7.1.10",
"vue-tsc": "^3.1.1",
"@tsconfig/node22": "^22.0.2"
}
}
5 changes: 5 additions & 0 deletions clients/web/apps/dashboard/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
100 changes: 100 additions & 0 deletions clients/web/apps/dashboard/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<script setup lang="ts">
import { computed, ref } from "vue";

type Role = "assistant" | "user";

interface Message {
id: number;
role: Role;
content: string;
}

const modelName = "Corvus Agent";
const _showConfig = ref(false);
const prompt = ref("");
const baseUrl = ref("http://127.0.0.1:3000");
const _pairingCode = ref("");
const _bearerToken = ref("");
const _webhookSecret = ref("");

const messages = ref<Message[]>([
{
id: 0,
role: "assistant",
content: `Hola, soy ${modelName}. ¿En qué puedo ayudarte?`,
},
]);

const _canSend = computed(() => prompt.value.trim().length > 0);

function _sendMessage() {
const text = prompt.value.trim();
if (!text) {
return;
}

messages.value.push({ id: Date.now(), role: "user", content: text });
messages.value.push({
id: Date.now() + 1,
role: "assistant",
content: `Procesando "${text}" con ${modelName}. Gateway: ${baseUrl.value}`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

User-supplied text interpolated directly into the mock response.

content: \Procesando "${text}" ...embeds raw user input into the assistant message string. While this is a mock and Vue's template rendering escapes HTML by default (so no XSS via{{ }}), be cautious if this content is ever rendered with v-html` or forwarded to a backend without sanitization. As per coding guidelines, "Check for vulnerabilities in encryption, crypto code, and configuration handling."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/web/apps/dashboard/src/App.vue` at line 44, The mock assistant
message is interpolating raw user input (text) into the template string in
App.vue (content: `Procesando "${text}" con ${modelName}. Gateway:
${baseUrl.value}`), so sanitize or escape text before embedding: create/ reuse
an escapeHtml or sanitizeWithDomPurify utility and use escapedText when
constructing the content string (e.g. replace occurrences of text with
escapedText), and validate/limit length of text if appropriate; ensure any place
that forwards this mock message (or renders it with v-html) uses the sanitized
version to prevent XSS or unsafe forwarding.

});
prompt.value = "";
}
</script>

<template>
<main class="min-h-screen bg-slate-50 text-slate-900">
<section class="mx-auto flex w-full max-w-4xl flex-col gap-4 p-4 md:p-6">
<header class="flex items-center justify-between rounded-xl border border-slate-200 bg-white p-4">
<div>
<h1 class="text-xl font-semibold">{{ modelName }}</h1>
<p class="text-sm text-slate-500">
{{ showConfig ? 'Configuración del gateway' : 'Simple AI chat' }}
</p>
</div>
<Button variant="ghost" size="sm" @click="showConfig = !showConfig">
{{ showConfig ? 'Volver al chat' : 'Config' }}
</Button>
</header>

<section
v-if="showConfig"
class="grid gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm"
>
<label class="space-y-1 text-sm">
<span class="font-medium">Base URL</span>
<Input v-model="baseUrl" placeholder="http://127.0.0.1:3000" />
</label>
<label class="space-y-1 text-sm">
<span class="font-medium">Pairing code</span>
<Input v-model="pairingCode" placeholder="Pairing code" />
</label>
<label class="space-y-1 text-sm">
<span class="font-medium">Bearer token</span>
<Input v-model="bearerToken" placeholder="Bearer token" type="password" />
</label>
<label class="space-y-1 text-sm">
<span class="font-medium">Webhook secret</span>
<Input v-model="webhookSecret" placeholder="Webhook secret" type="password" />
</label>
</section>

<section v-else class="flex min-h-[70vh] flex-col rounded-xl border border-slate-200 bg-white shadow-sm">
<div class="flex-1 space-y-3 overflow-y-auto p-4">
<ChatMessage
v-for="message in messages"
:key="message.id"
:role="message.role"
:content="message.content"
/>
</div>

<form class="flex gap-2 border-t border-slate-200 p-3" @submit.prevent="sendMessage">
<Input v-model="prompt" placeholder="Escribe un mensaje..." />
<Button type="submit" :disabled="!canSend">Enviar</Button>
</form>
</section>
</section>
</main>
</template>
19 changes: 19 additions & 0 deletions clients/web/apps/dashboard/src/components/chat/ChatMessage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup lang="ts">
defineProps<{
role: "assistant" | "user";
content: string;
}>();
</script>

<template>
<div :class="['flex w-full', role === 'user' ? 'justify-end' : 'justify-start']">
<div
:class="[
'max-w-[80%] rounded-xl px-4 py-3 text-sm shadow-sm',
role === 'user' ? 'bg-slate-900 text-slate-50' : 'bg-slate-100 text-slate-900',
]"
>
{{ content }}
</div>
</div>
</template>
46 changes: 46 additions & 0 deletions clients/web/apps/dashboard/src/components/ui/button/Button.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import { cva, type VariantProps } from "class-variance-authority";
import { computed } from "vue";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ring-offset-white",
{
variants: {
variant: {
default: "bg-slate-900 text-slate-50 hover:bg-slate-900/90",
ghost: "hover:bg-slate-100 hover:text-slate-900",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);

type ButtonVariants = VariantProps<typeof buttonVariants>;

const props = defineProps<{
class?: string;
variant?: ButtonVariants["variant"];
size?: ButtonVariants["size"];
type?: "button" | "submit" | "reset";
}>();

const _classes = computed(() =>
cn(buttonVariants({ variant: props.variant, size: props.size }), props.class)
);
</script>

<template>
<button :type="type ?? 'button'" :class="classes">
<slot />
</button>
</template>
33 changes: 33 additions & 0 deletions clients/web/apps/dashboard/src/components/ui/input/Input.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script setup lang="ts">
import { computed } from "vue";

import { cn } from "@/lib/utils";

const props = defineProps<{
class?: string;
modelValue?: string;
placeholder?: string;
type?: string;
}>();

const _emit = defineEmits<{
"update:modelValue": [value: string];
}>();

const _classes = computed(() =>
cn(
"flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
props.class
)
);
</script>

<template>
<input
:class="classes"
:type="type ?? 'text'"
:value="modelValue"
:placeholder="placeholder"
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
/>
</template>
6 changes: 6 additions & 0 deletions clients/web/apps/dashboard/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
6 changes: 6 additions & 0 deletions clients/web/apps/dashboard/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createApp } from "vue";

import App from "./App.vue";
import "./style.css";

createApp(App).mount("#app");
13 changes: 13 additions & 0 deletions clients/web/apps/dashboard/src/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@import "tailwindcss";

:root {
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

* {
box-sizing: border-box;
}

body {
margin: 0;
}
11 changes: 11 additions & 0 deletions clients/web/apps/dashboard/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
11 changes: 11 additions & 0 deletions clients/web/apps/dashboard/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}
10 changes: 10 additions & 0 deletions clients/web/apps/dashboard/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
},
"include": ["vite.config.ts"]
}
Loading
Loading