feat(matrix-web): add bots page with all 19 Matrix bots

- Add /bots route with bot overview grid
- Create BotCard component with expandable details
- Implement search and category filtering (AI, Productivity, Media, Lifestyle, Tools)
- Add bot data structure with commands, descriptions, and metadata
- Support starting chat with bots (creates DM or navigates to existing room)
- Add German and English translations
- Add robot icon to PillNavigation component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-02 15:36:49 +01:00
parent aba79f5c16
commit 23852cf605
7 changed files with 793 additions and 0 deletions

View file

@ -0,0 +1,166 @@
<script lang="ts">
import type { BotInfo } from '$lib/data/bots';
import {
Lock,
LockOpen,
CaretDown,
ChatCircle,
// Bot icons
Sparkle,
Robot,
CheckSquare,
CalendarBlank,
AddressBook,
Folders,
Image,
SpeakerHigh,
CloudArrowUp,
ForkKnife,
Plant,
Quotes,
TreeStructure,
Clock,
ChartBar,
MagnifyingGlass,
Cards,
PresentationChart,
} from '@manacore/shared-icons';
import { slide } from 'svelte/transition';
import { _ as t } from 'svelte-i18n';
import type { Component } from 'svelte';
interface Props {
bot: BotInfo;
onStartChat: () => void;
}
let { bot, onStartChat }: Props = $props();
let expanded = $state(false);
// Map icon names to components
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const iconMap: Record<string, Component<any>> = {
Sparkle,
ChatCircle,
Robot,
CheckSquare,
CalendarBlank,
AddressBook,
Folders,
Image,
SpeakerHigh,
CloudArrowUp,
ForkKnife,
Plant,
Quotes,
TreeStructure,
Clock,
ChartBar,
MagnifyingGlass,
Cards,
PresentationChart,
};
let IconComponent = $derived(iconMap[bot.icon] || Robot);
</script>
<div class="glass-card rounded-xl overflow-hidden border border-white/10">
<!-- Header (always visible) -->
<button
class="w-full p-4 text-left hover:bg-white/5 transition-colors cursor-pointer"
onclick={() => (expanded = !expanded)}
>
<div class="flex items-start gap-3">
<div class="p-3 rounded-lg bg-gradient-to-br {bot.color} shadow-lg flex-shrink-0">
<svelte:component this={IconComponent} size={24} class="text-white" weight="fill" />
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2">
<h3 class="font-semibold text-foreground truncate">{bot.name}</h3>
{#if bot.isGateway}
<span class="text-xs bg-violet-500/20 text-violet-400 px-2 py-0.5 rounded-full">
Gateway
</span>
{/if}
</div>
<p class="text-sm text-muted-foreground line-clamp-2 mt-0.5">{bot.description}</p>
</div>
<CaretDown
size={20}
class="text-muted-foreground transition-transform flex-shrink-0 {expanded
? 'rotate-180'
: ''}"
/>
</div>
<div class="mt-3 flex items-center gap-2 flex-wrap">
{#if bot.requiresAuth}
<span
class="text-xs bg-amber-500/20 text-amber-400 px-2 py-0.5 rounded-full flex items-center gap-1"
>
<Lock size={12} /> Login
</span>
{:else}
<span
class="text-xs bg-green-500/20 text-green-400 px-2 py-0.5 rounded-full flex items-center gap-1"
>
<LockOpen size={12} />
{$t('bots.free')}
</span>
{/if}
<span class="text-xs bg-primary/20 text-primary px-2 py-0.5 rounded-full">
{bot.commands.length}
{$t('bots.commands')}
</span>
</div>
</button>
<!-- Expandable Details -->
{#if expanded}
<div transition:slide={{ duration: 200 }} class="border-t border-white/10 p-4 space-y-4">
<!-- Long Description -->
{#if bot.longDescription}
<p class="text-sm text-muted-foreground">{bot.longDescription}</p>
{/if}
<!-- Commands -->
<div>
<h4 class="text-sm font-medium text-foreground mb-2">{$t('bots.commands')}</h4>
<div class="space-y-1.5 max-h-48 overflow-y-auto">
{#each bot.commands as cmd}
<div class="text-xs bg-black/20 rounded px-2 py-1.5">
<code class="text-primary font-mono">{cmd.command}</code>
{#if cmd.aliases?.length}
<span class="text-muted-foreground"> ({cmd.aliases.join(', ')})</span>
{/if}
<span class="text-muted-foreground ml-2">- {cmd.description}</span>
{#if cmd.example}
<div class="mt-1 text-muted-foreground/70 italic">
{$t('bots.example')}: <code class="text-foreground/60">{cmd.example}</code>
</div>
{/if}
</div>
{/each}
</div>
</div>
<!-- Matrix User ID -->
<div class="text-xs text-muted-foreground">
<span class="font-medium">Matrix:</span>
<code class="ml-1 text-foreground/60">{bot.matrixUserId}</code>
</div>
<!-- Chat Button -->
<button
class="w-full bg-gradient-to-r from-violet-500 to-purple-600 text-white rounded-lg py-2.5 flex items-center justify-center gap-2 font-medium hover:from-violet-600 hover:to-purple-700 transition-all shadow-lg hover:shadow-xl cursor-pointer"
onclick={(e) => {
e.stopPropagation();
onStartChat();
}}
>
<ChatCircle size={18} weight="fill" />
{$t('bots.startChat')}
</button>
</div>
{/if}
</div>

View file

@ -0,0 +1,424 @@
export interface BotCommand {
command: string;
aliases?: string[];
description: string;
example?: string;
}
export interface BotInfo {
id: string;
name: string;
matrixUserId: string;
description: string;
longDescription?: string;
icon: string;
color: string;
commands: BotCommand[];
category: 'productivity' | 'ai' | 'media' | 'lifestyle' | 'tools';
requiresAuth: boolean;
isGateway?: boolean;
}
export const BOTS: BotInfo[] = [
// AI & Chat
{
id: 'mana-bot',
name: 'Mana Bot',
matrixUserId: '@mana-bot:matrix.mana.how',
description: 'All-in-One Gateway zu allen Mana-Services',
longDescription:
'Der zentrale Hub, der alle anderen Bots vereint. Starte Chats, erstelle Bilder, verwalte Aufgaben und mehr - alles in einem Bot.',
icon: 'Sparkle',
color: 'from-violet-500 to-purple-600',
category: 'ai',
requiresAuth: true,
isGateway: true,
commands: [
{ command: '!help', aliases: ['!hilfe'], description: 'Zeigt alle verfügbaren Befehle' },
{ command: '!chat', description: 'Startet einen KI-Chat' },
{ command: '!image', aliases: ['!bild'], description: 'Generiert ein Bild mit KI' },
{ command: '!todo', description: 'Verwaltet Aufgaben' },
{ command: '!calendar', aliases: ['!cal'], description: 'Kalender-Operationen' },
],
},
{
id: 'chat-bot',
name: 'Chat Bot',
matrixUserId: '@chat-bot:matrix.mana.how',
description: 'KI-Assistent powered by Claude und anderen LLMs',
longDescription:
'Dein persönlicher KI-Assistent für Fragen, Texterstellung, Zusammenfassungen und kreative Aufgaben. Nutzt verschiedene KI-Modelle.',
icon: 'ChatCircle',
color: 'from-blue-500 to-cyan-500',
category: 'ai',
requiresAuth: true,
commands: [
{
command: '!chat',
description: 'Startet eine Konversation',
example: '!chat Erkläre mir Quantencomputing',
},
{ command: '!model', description: 'Wechselt das KI-Modell', example: '!model gpt-4' },
{ command: '!clear', description: 'Löscht den Chat-Verlauf' },
{ command: '!system', description: 'Setzt einen System-Prompt' },
],
},
{
id: 'ollama-bot',
name: 'Ollama Bot',
matrixUserId: '@ollama-bot:matrix.mana.how',
description: 'Lokale KI-Modelle via Ollama',
longDescription:
'Chatte mit lokal gehosteten Open-Source KI-Modellen. Vollständig privat, keine Daten verlassen den Server.',
icon: 'Robot',
color: 'from-emerald-500 to-teal-600',
category: 'ai',
requiresAuth: false,
commands: [
{ command: '!ollama', description: 'Startet einen Chat', example: '!ollama Hallo!' },
{ command: '!models', description: 'Listet verfügbare Modelle' },
{ command: '!switch', description: 'Wechselt das Modell', example: '!switch llama3' },
],
},
// Productivity
{
id: 'todo-bot',
name: 'Todo Bot',
matrixUserId: '@todo-bot:matrix.mana.how',
description: 'Aufgabenverwaltung und To-Do Listen',
longDescription:
'Verwalte deine Aufgaben direkt im Chat. Erstelle, bearbeite und erledige Todos mit einfachen Befehlen.',
icon: 'CheckSquare',
color: 'from-green-500 to-emerald-600',
category: 'productivity',
requiresAuth: true,
commands: [
{
command: '!add',
description: 'Fügt eine neue Aufgabe hinzu',
example: '!add Einkaufen gehen',
},
{ command: '!list', aliases: ['!todos'], description: 'Zeigt alle Aufgaben' },
{ command: '!done', description: 'Markiert Aufgabe als erledigt', example: '!done 1' },
{
command: '!delete',
aliases: ['!del'],
description: 'Löscht eine Aufgabe',
example: '!delete 1',
},
{ command: '!clear', description: 'Löscht alle erledigten Aufgaben' },
],
},
{
id: 'calendar-bot',
name: 'Calendar Bot',
matrixUserId: '@calendar-bot:matrix.mana.how',
description: 'Terminverwaltung und Erinnerungen',
longDescription:
'Plane Termine, setze Erinnerungen und behalte deinen Zeitplan im Blick - alles per Chat-Befehl.',
icon: 'CalendarBlank',
color: 'from-orange-500 to-amber-600',
category: 'productivity',
requiresAuth: true,
commands: [
{
command: '!event',
description: 'Erstellt einen Termin',
example: '!event Meeting morgen 14:00',
},
{ command: '!today', description: 'Zeigt heutige Termine' },
{ command: '!week', description: 'Zeigt Termine dieser Woche' },
{ command: '!remind', description: 'Setzt eine Erinnerung', example: '!remind 30min Anruf' },
],
},
{
id: 'contacts-bot',
name: 'Contacts Bot',
matrixUserId: '@contacts-bot:matrix.mana.how',
description: 'Kontaktverwaltung und Adressbuch',
longDescription:
'Speichere und finde Kontaktinformationen schnell. Durchsuche dein Adressbuch direkt im Chat.',
icon: 'AddressBook',
color: 'from-indigo-500 to-blue-600',
category: 'productivity',
requiresAuth: true,
commands: [
{ command: '!find', description: 'Sucht nach Kontakten', example: '!find Max Mustermann' },
{ command: '!add', description: 'Fügt einen Kontakt hinzu' },
{ command: '!all', description: 'Listet alle Kontakte' },
],
},
{
id: 'project-doc-bot',
name: 'Project Doc Bot',
matrixUserId: '@project-doc-bot:matrix.mana.how',
description: 'Projektdokumentation und Wissensbasis',
longDescription:
'Durchsuche Projektdokumentationen, finde Code-Beispiele und erhalte Antworten basierend auf deiner Wissensbasis.',
icon: 'Folders',
color: 'from-purple-500 to-violet-600',
category: 'productivity',
requiresAuth: true,
commands: [
{
command: '!search',
description: 'Durchsucht die Dokumentation',
example: '!search API authentication',
},
{ command: '!projects', description: 'Listet verfügbare Projekte' },
{ command: '!select', description: 'Wählt ein Projekt aus', example: '!select manacore' },
],
},
// Media
{
id: 'picture-bot',
name: 'Picture Bot',
matrixUserId: '@picture-bot:matrix.mana.how',
description: 'KI-Bildgenerierung mit verschiedenen Modellen',
longDescription:
'Erstelle beeindruckende Bilder mit KI. Unterstützt verschiedene Stile und Modelle wie Stable Diffusion und DALL-E.',
icon: 'Image',
color: 'from-pink-500 to-rose-600',
category: 'media',
requiresAuth: true,
commands: [
{
command: '!image',
aliases: ['!bild'],
description: 'Generiert ein Bild',
example: '!image Ein Sonnenuntergang am Meer',
},
{ command: '!style', description: 'Wählt einen Stil', example: '!style anime' },
{ command: '!size', description: 'Setzt die Bildgröße', example: '!size 1024x1024' },
],
},
{
id: 'tts-bot',
name: 'TTS Bot',
matrixUserId: '@tts-bot:matrix.mana.how',
description: 'Text-to-Speech Sprachausgabe',
longDescription:
'Wandle Text in natürlich klingende Sprache um. Unterstützt verschiedene Stimmen und Sprachen.',
icon: 'SpeakerHigh',
color: 'from-cyan-500 to-sky-600',
category: 'media',
requiresAuth: true,
commands: [
{
command: '!speak',
aliases: ['!say'],
description: 'Spricht Text vor',
example: '!speak Hallo Welt',
},
{ command: '!voice', description: 'Wählt eine Stimme', example: '!voice nova' },
{ command: '!lang', description: 'Setzt die Sprache', example: '!lang de' },
],
},
{
id: 'storage-bot',
name: 'Storage Bot',
matrixUserId: '@storage-bot:matrix.mana.how',
description: 'Cloud-Speicher und Dateiverwaltung',
longDescription:
'Verwalte deine Dateien in der Cloud. Lade hoch, teile und organisiere direkt aus dem Chat.',
icon: 'CloudArrowUp',
color: 'from-slate-500 to-zinc-600',
category: 'media',
requiresAuth: true,
commands: [
{ command: '!upload', description: 'Lädt eine Datei hoch' },
{ command: '!files', description: 'Listet deine Dateien' },
{ command: '!share', description: 'Teilt eine Datei', example: '!share document.pdf' },
{ command: '!delete', description: 'Löscht eine Datei' },
],
},
// Lifestyle
{
id: 'nutriphi-bot',
name: 'NutriPhi Bot',
matrixUserId: '@nutriphi-bot:matrix.mana.how',
description: 'Ernährungstracking und Mahlzeiten-Analyse',
longDescription:
'Tracke deine Ernährung, analysiere Mahlzeiten per Foto und erhalte Nährwertinformationen.',
icon: 'ForkKnife',
color: 'from-lime-500 to-green-600',
category: 'lifestyle',
requiresAuth: true,
commands: [
{
command: '!log',
description: 'Protokolliert eine Mahlzeit',
example: '!log 2 Äpfel, 1 Sandwich',
},
{ command: '!today', description: 'Zeigt heutige Kalorien' },
{ command: '!analyze', description: 'Analysiert ein Essens-Foto' },
],
},
{
id: 'planta-bot',
name: 'Planta Bot',
matrixUserId: '@planta-bot:matrix.mana.how',
description: 'Pflanzenidentifikation und Pflege-Tipps',
longDescription:
'Identifiziere Pflanzen per Foto und erhalte Pflege-Anleitungen. Perfekt für Hobbygärtner.',
icon: 'Plant',
color: 'from-green-600 to-emerald-700',
category: 'lifestyle',
requiresAuth: true,
commands: [
{ command: '!identify', description: 'Identifiziert eine Pflanze per Foto' },
{ command: '!care', description: 'Zeigt Pflegetipps', example: '!care Monstera' },
{ command: '!water', description: 'Erinnerung zum Gießen setzen' },
],
},
{
id: 'zitare-bot',
name: 'Zitare Bot',
matrixUserId: '@zitare-bot:matrix.mana.how',
description: 'Tägliche Inspiration und Weisheiten',
longDescription:
'Erhalte inspirierende Zitate und Weisheiten. Perfekt für den täglichen Motivationsschub.',
icon: 'Quotes',
color: 'from-amber-500 to-orange-600',
category: 'lifestyle',
requiresAuth: true,
commands: [
{ command: '!quote', aliases: ['!zitat'], description: 'Zeigt ein zufälliges Zitat' },
{ command: '!daily', description: 'Aktiviert tägliche Zitate' },
{ command: '!topic', description: 'Zitat zu einem Thema', example: '!topic Erfolg' },
],
},
{
id: 'skilltree-bot',
name: 'SkillTree Bot',
matrixUserId: '@skilltree-bot:matrix.mana.how',
description: 'Fähigkeiten-Tracking und Lernfortschritt',
longDescription:
'Verfolge deinen Lernfortschritt, setze Ziele und entwickle deine Fähigkeiten systematisch weiter.',
icon: 'TreeStructure',
color: 'from-yellow-500 to-amber-600',
category: 'lifestyle',
requiresAuth: true,
commands: [
{ command: '!skills', description: 'Zeigt deine Fähigkeiten' },
{ command: '!add', description: 'Fügt eine Fähigkeit hinzu', example: '!add TypeScript' },
{
command: '!progress',
description: 'Protokolliert Fortschritt',
example: '!progress TypeScript +2h',
},
],
},
// Tools
{
id: 'clock-bot',
name: 'Clock Bot',
matrixUserId: '@clock-bot:matrix.mana.how',
description: 'Zeiterfassung und Zeitzonen',
longDescription:
'Tracke Arbeitszeiten, konvertiere Zeitzonen und setze Timer für Fokus-Sessions.',
icon: 'Clock',
color: 'from-blue-600 to-indigo-700',
category: 'tools',
requiresAuth: true,
commands: [
{ command: '!start', description: 'Startet Zeiterfassung' },
{ command: '!stop', description: 'Stoppt Zeiterfassung' },
{ command: '!time', description: 'Zeigt aktuelle Zeit in Zonen', example: '!time NYC' },
{ command: '!timer', description: 'Setzt einen Timer', example: '!timer 25m Pomodoro' },
],
},
{
id: 'stats-bot',
name: 'Stats Bot',
matrixUserId: '@stats-bot:matrix.mana.how',
description: 'Nutzungsstatistiken und Analytics',
longDescription:
'Erhalte Einblicke in deine Nutzung der Mana-Services. Statistiken, Trends und Zusammenfassungen.',
icon: 'ChartBar',
color: 'from-fuchsia-500 to-pink-600',
category: 'tools',
requiresAuth: true,
commands: [
{ command: '!stats', description: 'Zeigt Nutzungsstatistiken' },
{ command: '!usage', description: 'Credits-Verbrauch diese Woche' },
{ command: '!top', description: 'Meistgenutzte Features' },
],
},
{
id: 'questions-bot',
name: 'Questions Bot',
matrixUserId: '@questions-bot:matrix.mana.how',
description: 'Websuche und Fakten-Recherche',
longDescription:
'Durchsuche das Web und erhalte fundierte Antworten mit Quellenangaben. Perfekt für Recherchen.',
icon: 'MagnifyingGlass',
color: 'from-teal-500 to-cyan-600',
category: 'tools',
requiresAuth: true,
commands: [
{
command: '!search',
aliases: ['!q'],
description: 'Sucht im Web',
example: '!search Wetter Berlin',
},
{ command: '!wiki', description: 'Sucht auf Wikipedia', example: '!wiki Photosynthese' },
{ command: '!news', description: 'Aktuelle Nachrichten' },
],
},
{
id: 'manadeck-bot',
name: 'ManaDeck Bot',
matrixUserId: '@manadeck-bot:matrix.mana.how',
description: 'Lernkarten und Spaced Repetition',
longDescription:
'Erstelle und lerne mit Karteikarten. Nutzt Spaced Repetition für optimales Lernen.',
icon: 'Cards',
color: 'from-violet-600 to-purple-700',
category: 'tools',
requiresAuth: true,
commands: [
{ command: '!learn', description: 'Startet eine Lernsession' },
{ command: '!add', description: 'Fügt eine Karte hinzu', example: '!add Frage | Antwort' },
{ command: '!decks', description: 'Listet deine Decks' },
{ command: '!stats', description: 'Zeigt Lernfortschritt' },
],
},
{
id: 'presi-bot',
name: 'Presi Bot',
matrixUserId: '@presi-bot:matrix.mana.how',
description: 'Präsentationen erstellen mit KI',
longDescription:
'Erstelle professionelle Präsentationen mit KI-Unterstützung. Generiere Folien aus Text oder Themen.',
icon: 'PresentationChart',
color: 'from-red-500 to-rose-600',
category: 'tools',
requiresAuth: true,
commands: [
{
command: '!create',
description: 'Erstellt eine Präsentation',
example: '!create Thema: KI im Alltag',
},
{ command: '!slides', description: 'Zeigt deine Präsentationen' },
{ command: '!export', description: 'Exportiert als PDF/PPTX' },
],
},
];
export const CATEGORIES = [
{ id: 'all', label: 'Alle' },
{ id: 'productivity', label: 'Produktivität' },
{ id: 'ai', label: 'KI & Chat' },
{ id: 'media', label: 'Medien' },
{ id: 'lifestyle', label: 'Lifestyle' },
{ id: 'tools', label: 'Tools' },
] as const;
export type BotCategory = (typeof CATEGORIES)[number]['id'];

View file

@ -5,6 +5,7 @@
},
"nav": {
"chat": "Chat",
"bots": "Bots",
"settings": "Einstellungen"
},
"auth": {
@ -21,5 +22,25 @@
"typeMessage": "Nachricht schreiben...",
"noRooms": "Noch keine Räume",
"noMessages": "Noch keine Nachrichten"
},
"bots": {
"title": "Bots",
"subtitle": "Entdecke alle verfügbaren Bot-Assistenten",
"search": "Bot suchen...",
"startChat": "Chat starten",
"commands": "Befehle",
"example": "Beispiel",
"free": "Frei",
"requiresLogin": "Erfordert Anmeldung",
"noResults": "Keine Bots gefunden",
"found": "gefunden",
"categories": {
"all": "Alle",
"productivity": "Produktivität",
"ai": "KI & Chat",
"media": "Medien",
"lifestyle": "Lifestyle",
"tools": "Tools"
}
}
}

View file

@ -5,6 +5,7 @@
},
"nav": {
"chat": "Chat",
"bots": "Bots",
"settings": "Settings"
},
"auth": {
@ -21,5 +22,25 @@
"typeMessage": "Type a message...",
"noRooms": "No rooms yet",
"noMessages": "No messages yet"
},
"bots": {
"title": "Bots",
"subtitle": "Discover all available bot assistants",
"search": "Search bots...",
"startChat": "Start Chat",
"commands": "Commands",
"example": "Example",
"free": "Free",
"requiresLogin": "Requires login",
"noResults": "No bots found",
"found": "found",
"categories": {
"all": "All",
"productivity": "Productivity",
"ai": "AI & Chat",
"media": "Media",
"lifestyle": "Lifestyle",
"tools": "Tools"
}
}
}

View file

@ -67,6 +67,7 @@
// Navigation items for Matrix
const navItems: PillNavItem[] = [
{ href: '/chat', label: 'Chat', icon: 'home' },
{ href: '/bots', label: 'Bots', icon: 'robot' },
{ href: '/settings', label: 'Einstellungen', icon: 'settings' },
];

View file

@ -0,0 +1,158 @@
<script lang="ts">
import { BOTS, CATEGORIES, type BotCategory, type BotInfo } from '$lib/data/bots';
import { matrixStore } from '$lib/matrix';
import { goto } from '$app/navigation';
import { _ as t } from 'svelte-i18n';
import { MagnifyingGlass, Robot, CircleNotch } from '@manacore/shared-icons';
import BotCard from '$lib/components/bots/BotCard.svelte';
let search = $state('');
let selectedCategory = $state<BotCategory>('all');
let startingChat = $state<string | null>(null);
let filteredBots = $derived(
BOTS.filter((bot) => {
const searchLower = search.toLowerCase();
const matchesSearch =
bot.name.toLowerCase().includes(searchLower) ||
bot.description.toLowerCase().includes(searchLower) ||
bot.commands.some((cmd) => cmd.command.toLowerCase().includes(searchLower));
const matchesCategory = selectedCategory === 'all' || bot.category === selectedCategory;
return matchesSearch && matchesCategory;
})
);
let categoryLabels = $derived(
CATEGORIES.map((cat) => ({
...cat,
label: $t(`bots.categories.${cat.id}`),
}))
);
async function startChat(bot: BotInfo) {
startingChat = bot.id;
try {
// Check if a DM room with this bot already exists
const existingRoom = matrixStore.directRooms.find((r) => r.dmUserId === bot.matrixUserId);
if (existingRoom) {
// Select existing room
matrixStore.selectRoom(existingRoom.id);
} else {
// Create new DM room with the bot
const roomId = await matrixStore.createRoom({
isDirect: true,
invite: [bot.matrixUserId],
});
if (roomId) {
matrixStore.selectRoom(roomId);
}
}
// Navigate to chat
goto('/chat');
} catch (err) {
console.error('Failed to start chat with bot:', err);
} finally {
startingChat = null;
}
}
</script>
<svelte:head>
<title>{$t('bots.title')} - Manalink</title>
</svelte:head>
<div class="h-full overflow-y-auto bg-background">
<div class="max-w-6xl mx-auto px-4 py-6 pb-24 md:pb-6">
<!-- Header -->
<div class="mb-6">
<div class="flex items-center gap-3 mb-2">
<div class="p-2.5 rounded-xl bg-gradient-to-br from-violet-500 to-purple-600 shadow-lg">
<Robot size={28} class="text-white" weight="fill" />
</div>
<div>
<h1 class="text-2xl font-bold text-foreground">{$t('bots.title')}</h1>
<p class="text-muted-foreground text-sm">{$t('bots.subtitle')}</p>
</div>
</div>
</div>
<!-- Search -->
<div class="mb-4">
<div class="relative">
<MagnifyingGlass
size={20}
class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
/>
<input
type="text"
bind:value={search}
placeholder={$t('bots.search')}
class="w-full pl-10 pr-4 py-2.5 rounded-xl bg-white/5 border border-white/10 text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary/50 transition-all"
/>
</div>
</div>
<!-- Category Tabs -->
<div class="mb-6 overflow-x-auto scrollbar-hide">
<div class="flex gap-2 min-w-max pb-1">
{#each categoryLabels as category}
<button
class="px-4 py-2 rounded-full text-sm font-medium transition-all whitespace-nowrap cursor-pointer
{selectedCategory === category.id
? 'bg-gradient-to-r from-violet-500 to-purple-600 text-white shadow-lg'
: 'bg-white/5 text-muted-foreground hover:bg-white/10 hover:text-foreground border border-white/10'}"
onclick={() => (selectedCategory = category.id as BotCategory)}
>
{category.label}
</button>
{/each}
</div>
</div>
<!-- Bot Grid -->
{#if filteredBots.length === 0}
<div class="text-center py-12">
<Robot size={48} class="text-muted-foreground/50 mx-auto mb-4" />
<p class="text-muted-foreground">{$t('bots.noResults')}</p>
</div>
{:else}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{#each filteredBots as bot (bot.id)}
<div class="relative">
<BotCard {bot} onStartChat={() => startChat(bot)} />
{#if startingChat === bot.id}
<div
class="absolute inset-0 bg-background/80 backdrop-blur-sm rounded-xl flex items-center justify-center"
>
<CircleNotch size={32} class="text-primary animate-spin" />
</div>
{/if}
</div>
{/each}
</div>
{/if}
<!-- Bot Count -->
<div class="mt-6 text-center text-sm text-muted-foreground">
{filteredBots.length}
{filteredBots.length === 1 ? 'Bot' : 'Bots'}
{#if selectedCategory !== 'all' || search}
{$t('bots.found')}
{/if}
</div>
</div>
</div>
<style>
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>