diff --git a/components/auth/user-menu.tsx b/components/auth/user-menu.tsx index 3998c42..f2b68b1 100644 --- a/components/auth/user-menu.tsx +++ b/components/auth/user-menu.tsx @@ -9,6 +9,7 @@ import { CircleUser, CreditCard, Gift, + LogIn, LogOut, MessageCircleQuestion, Monitor, @@ -31,7 +32,7 @@ import { const FOOTER_LINKS = [ { href: "/settings/preferences", label: "Profile", Icon: CircleUser }, { href: "/settings", label: "Account Settings", Icon: SettingsIcon }, - { href: "/settings/billing", label: "Pricing", Icon: CreditCard }, + { href: "/settings/billing", label: "Pricing", Icon: CreditCard, external: true }, { href: "https://github.com/OpenSIN-Code/SIN-Code-WebUI-v2#readme", label: "Documentation", @@ -58,35 +59,19 @@ export function UserMenu() { const { data: session, isPending } = useSession() const { theme, setTheme } = useTheme() - if (isPending) { - return ( -
- - … - -
- ) - } - - if (!session?.user) { - return ( - - ) - } - - const user = session.user as { name?: string | null; email?: string | null } - const displayName = user.name || user.email || "User" - const initial = (user.name || user.email || "U").charAt(0).toUpperCase() - const email = user.email || "" + // Show trigger even while pending / unauthenticated — the v0-style dropdown + // is always present at the sidebar footer. + const user = session?.user as + | { name?: string | null; email?: string | null } + | undefined + const isLoggedIn = Boolean(user?.name || user?.email) + const displayName = isLoggedIn + ? user!.name || user!.email || "User" + : "Anmelden" + const initial = isLoggedIn + ? (user!.name || user!.email || "U").charAt(0).toUpperCase() + : "?" + const email = user?.email || "" async function handleSignOut() { const { signOut } = await import("@/lib/auth/client") @@ -95,32 +80,50 @@ export function UserMenu() { router.refresh() } + function handleSignIn() { + router.push("/login") + } + return ( } > - {initial} + {isPending ? "…" : initial} {displayName} -
- - {displayName} - - {email ? ( - {email} - ) : null} -
+ {isLoggedIn ? ( +
+ + {displayName} + + {email ? ( + {email} + ) : null} +
+ ) : ( +
+ + Nicht angemeldet + + + Melde dich an, um Chats zu speichern + +
+ )} @@ -129,7 +132,12 @@ export function UserMenu() { key={label} render={ external ? ( - + {label} @@ -194,7 +202,7 @@ export function UserMenu() { - {/* Chat Position — stub (single layout) */} + {/* Chat Position — stub */}
Chat Position - - - Sign Out - - } - /> + {isLoggedIn ? ( + + + Sign Out + + } + /> + ) : ( + + + Anmelden + + } + /> + )} diff --git a/components/chat/chat-view.tsx b/components/chat/chat-view.tsx index f20710c..a7d2076 100644 --- a/components/chat/chat-view.tsx +++ b/components/chat/chat-view.tsx @@ -1,12 +1,15 @@ "use client" -import { useRef, useEffect } from "react" +import { useRef, useEffect, useState } from "react" +import { Check, Copy, SquareTerminal } from "lucide-react" import { Message } from "@/components/chat/message" import { ThinkingIndicator, LoadingDots } from "@/components/chat/thinking-indicator" import { ToolCall } from "@/components/chat/tool-call" import { PromptComposer } from "@/components/chat/prompt-composer" import { MarkdownMessage } from "@/components/chat/markdown-message" import { ChatHeader } from "@/components/chat/chat-header" +import { DashedSpinner, Starburst } from "@/components/icons" +import { cn } from "@/lib/utils" export interface ChatPart { type: "text" | "tool" @@ -32,6 +35,51 @@ interface ChatViewProps { title?: string } +/* v0-style code block with copy button (preserved from initial commit) */ +function CopyCodeBlock({ body, lang = "code" }: { body: string; lang?: string }) { + const [copied, setCopied] = useState(false) + async function handleCopy() { + try { + await navigator.clipboard.writeText(body) + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } catch { + /* clipboard not available */ + } + } + return ( +
+
+
+ + {lang} +
+ +
+
+        {body}
+      
+
+ ) +} + +/* v0-style tool badge (preserved from initial commit) */ +function ToolBadge({ name }: { name: string }) { + return ( +
+ + {name} +
+ ) +} + export function ChatView({ messages, status, @@ -48,19 +96,22 @@ export function ChatView({ const isStreaming = status === "streaming" const lastMessage = messages[messages.length - 1] + const hasMessages = messages.length > 0 return (
-
- {messages.length === 0 && ( -
-

+
+ {!hasMessages && ( +
+ +

What do you want to create?

-

+

Ask the SIN-Code agent to build, fix, or explain anything. + Press Enter to send.

)} @@ -68,6 +119,18 @@ export function ChatView({ {messages.map((msg) => { const streamingThis = isStreaming && msg.id === lastMessage?.id && msg.role === "assistant" + if (msg.role === "user") { + return ( +
+
+ {msg.parts + .filter((p) => p.type === "text") + .map((p) => p.text) + .join("")} +
+
+ ) + } return (