diff --git a/ui/index.html b/ui/index.html index be1ac68..311dc0c 100644 --- a/ui/index.html +++ b/ui/index.html @@ -1,52 +1,42 @@ - + - + - + - + - + - + - -
- diff --git a/ui/rsbuild.config.ts b/ui/rsbuild.config.ts index bb8a4a9..cfe42ea 100644 --- a/ui/rsbuild.config.ts +++ b/ui/rsbuild.config.ts @@ -133,6 +133,12 @@ export default defineConfig({ "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }, + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true, + }, + }, }, tools: { rspack: { @@ -174,7 +180,7 @@ export default defineConfig({ // ? `${bosConfig.app.ui.production}/` // : "auto", filename: { - css: "static/css/[name].css", + css: "[name].css", }, copy: [ { diff --git a/ui/src/components/builders/BuilderDetails.tsx b/ui/src/components/builders/BuilderDetails.tsx new file mode 100644 index 0000000..5ddd89e --- /dev/null +++ b/ui/src/components/builders/BuilderDetails.tsx @@ -0,0 +1,174 @@ +/** + * Builder Details Component + * Right panel showing selected builder info + */ + +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import type { Builder } from "./BuilderListItem"; + +interface BuilderDetailsProps { + builder: Builder; +} + +export function BuilderDetails({ builder }: BuilderDetailsProps) { + return ( +
+
+ {/* Header */} + + + {/* Skills */} + + + {/* About */} + + + {/* Projects */} + + + {/* Socials */} + {builder.socials && } +
+
+ ); +} + +function BuilderHeader({ builder }: { builder: Builder }) { + return ( +
+ + + + {builder.displayName.slice(0, 2).toUpperCase()} + + +
+

+ {builder.displayName} +

+

{builder.accountId}

+ + {builder.role} + +
+
+ ); +} + +function BuilderSkills({ tags }: { tags: string[] }) { + return ( +
+

+ Skills +

+
+ {tags.map((tag) => ( + + {tag} + + ))} +
+
+ ); +} + +function BuilderAbout({ description }: { description: string }) { + return ( +
+

+ About +

+

{description}

+
+ ); +} + +function BuilderProjects({ + projects, +}: { + projects: { name: string; description: string; status: string }[]; +}) { + return ( +
+

+ Building +

+
+ {projects.map((project) => ( +
+
+ + {project.name} + + +
+

+ {project.description} +

+
+ ))} +
+
+ ); +} + +function ProjectStatus({ status }: { status: string }) { + const statusClass = + status === "Active" + ? "bg-primary/30 text-primary border-primary/40" + : status === "In Development" + ? "bg-accent/30 text-accent border-accent/40" + : status === "Beta" + ? "bg-blue-500/30 text-blue-400 border-blue-500/40" + : "bg-muted text-muted-foreground border-border"; + + return ( + + {status} + + ); +} + +function BuilderSocials({ + socials, +}: { + socials: { github?: string; twitter?: string }; +}) { + if (!socials.github && !socials.twitter) return null; + + return ( +
+

+ Connect +

+
+ {socials.github && ( + + github/{socials.github} + + )} + {socials.twitter && ( + + @{socials.twitter} + + )} +
+
+ ); +} diff --git a/ui/src/components/builders/BuilderList.tsx b/ui/src/components/builders/BuilderList.tsx new file mode 100644 index 0000000..e6c6056 --- /dev/null +++ b/ui/src/components/builders/BuilderList.tsx @@ -0,0 +1,36 @@ +/** + * Builder List Component + * Left panel showing all builders + */ + +import { BuilderListItem, type Builder } from "./BuilderListItem"; + +interface BuilderListProps { + builders: Builder[]; + selectedId: string; + onSelect: (builder: Builder) => void; +} + +export function BuilderList({ builders, selectedId, onSelect }: BuilderListProps) { + return ( +
+
+ + {builders.length} Legionnaires + +
+
+
+ {builders.map((builder) => ( + onSelect(builder)} + /> + ))} +
+
+
+ ); +} diff --git a/ui/src/components/builders/BuilderListItem.tsx b/ui/src/components/builders/BuilderListItem.tsx new file mode 100644 index 0000000..6bce4c8 --- /dev/null +++ b/ui/src/components/builders/BuilderListItem.tsx @@ -0,0 +1,65 @@ +/** + * Builder List Item Component + * Single builder entry in the list + */ + +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; + +export interface Builder { + id: string; + accountId: string; + displayName: string; + avatar: string | null; + role: string; + tags: string[]; + description: string; + projects: { + name: string; + description: string; + status: string; + }[]; + socials: { + github?: string; + twitter?: string; + }; +} + +interface BuilderListItemProps { + builder: Builder; + isSelected: boolean; + onSelect: () => void; +} + +export function BuilderListItem({ builder, isSelected, onSelect }: BuilderListItemProps) { + return ( + + ); +} diff --git a/ui/src/components/builders/index.ts b/ui/src/components/builders/index.ts new file mode 100644 index 0000000..c1bfebb --- /dev/null +++ b/ui/src/components/builders/index.ts @@ -0,0 +1,3 @@ +export { BuilderList } from "./BuilderList"; +export { BuilderDetails } from "./BuilderDetails"; +export { BuilderListItem, type Builder } from "./BuilderListItem"; diff --git a/ui/src/components/chat/ChatHeader.tsx b/ui/src/components/chat/ChatHeader.tsx new file mode 100644 index 0000000..6b8f084 --- /dev/null +++ b/ui/src/components/chat/ChatHeader.tsx @@ -0,0 +1,46 @@ +/** + * Chat Header Component + * Shows conversation info and controls + */ + +import { RankBadge } from "./RankBadge"; + +interface ChatHeaderProps { + conversationId: string | null; + isStreaming: boolean; + onNewConversation: () => void; +} + +export function ChatHeader({ + conversationId, + isStreaming, + onNewConversation, +}: ChatHeaderProps) { + return ( +
+
+

Chat

+ {conversationId && ( + + {conversationId.slice(0, 8)} + + )} + {isStreaming && ( + + + streaming + + )} +
+
+ + +
+
+ ); +} diff --git a/ui/src/components/chat/ChatInput.tsx b/ui/src/components/chat/ChatInput.tsx index c5b614d..f3f1e51 100644 --- a/ui/src/components/chat/ChatInput.tsx +++ b/ui/src/components/chat/ChatInput.tsx @@ -1,8 +1,6 @@ /** - * ChatInput Component - * - * Input field with send/stop button for the chat interface. - * Supports keyboard shortcuts (Enter to send). + * Chat Input Component + * Input field with send/stop button for the chat interface */ import { useState, useRef, useEffect } from "react"; @@ -35,7 +33,7 @@ export function ChatInput({ useEffect(() => { if (inputRef.current) { inputRef.current.style.height = "auto"; - inputRef.current.style.height = `${Math.min(inputRef.current.scrollHeight, 150)}px`; + inputRef.current.style.height = `${Math.min(inputRef.current.scrollHeight, 120)}px`; } }, [value]); @@ -55,8 +53,8 @@ export function ChatInput({ }; return ( -
-
+
+