Skip to content

Commit 2f18732

Browse files
authored
feat: implement full-page standalone chat view #157 (#162)
1 parent 8456771 commit 2f18732

3 files changed

Lines changed: 96 additions & 19 deletions

File tree

src/app/chat/page.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use client";
2+
3+
import ChatComponent from "@/components/ChatComponent";
4+
import { Button } from "@/components/ui/button";
5+
import { ArrowLeft, Maximize2 } from "lucide-react";
6+
import Link from "next/link";
7+
8+
export default function ChatPage() {
9+
return (
10+
<div className="flex h-screen w-full bg-zinc-950 overflow-hidden flex-col">
11+
{/* Header for full-page chat */}
12+
<header className="flex items-center justify-between px-6 py-4 border-b border-zinc-800 bg-black">
13+
<div className="flex items-center gap-4">
14+
<Link href="/k8s-dashboard">
15+
<Button variant="ghost" size="icon" className="text-zinc-400 hover:text-white">
16+
<ArrowLeft className="w-5 h-5" />
17+
</Button>
18+
</Link>
19+
<div className="flex items-center gap-2">
20+
<Maximize2 className="w-5 h-5 text-[#368dab]" />
21+
<h1 className="text-xl font-bold text-white tracking-tight">Full-Page Chat</h1>
22+
</div>
23+
</div>
24+
<div className="text-xs text-zinc-500 font-mono hidden sm:block">
25+
Distraction-free Kubernetes Assistant
26+
</div>
27+
</header>
28+
29+
{/* Main Chat Area */}
30+
<main className="flex-1 relative overflow-hidden flex justify-center p-4">
31+
<div className="w-full h-full">
32+
<ChatComponent isFullPage={true} />
33+
</div>
34+
</main>
35+
</div>
36+
);
37+
}

src/app/k8s-dashboard/Sidebar.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import {
1616
Clock,
1717
Play,
1818
Lock,
19-
Zap
19+
Zap,
20+
MessageSquareShare
2021
} from "lucide-react";
2122
import Image from "next/image";
2223
import { useState, useEffect } from "react";
24+
import Link from "next/link";
2325

2426
export type ToolType =
2527
| "pod-resources"
@@ -122,6 +124,18 @@ export default function Sidebar({
122124
</Button>
123125
))}
124126
</div>
127+
128+
<div className="mt-6 pt-6 border-t border-zinc-800">
129+
<Link href="/chat" target="_blank" className="w-full">
130+
<Button
131+
variant="outline"
132+
className="w-full justify-start gap-2 border-zinc-800 hover:bg-zinc-800 text-zinc-400 hover:text-white"
133+
>
134+
<MessageSquareShare className="w-4 h-4" />
135+
<span>Full-Page Chat</span>
136+
</Button>
137+
</Link>
138+
</div>
125139
</div>
126140
</aside>
127141
);

src/components/ChatComponent.tsx

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useState, useRef, useEffect, FormEvent } from "react";
44
import { Button } from "./ui/button";
55
import { Input } from "./ui/input";
66
import { Card } from "./ui/card";
7+
import { cn } from "@/lib/utils";
78
import {
89
Send,
910
X,
@@ -19,6 +20,7 @@ import {
1920
Trash2,
2021
RotateCcw,
2122
MessageSquare,
23+
ExternalLink,
2224
} from "lucide-react";
2325
import {
2426
DropdownMenu,
@@ -30,10 +32,12 @@ import { useChat } from "./ChatContext";
3032
import type { ChatSession } from "./ChatContext";
3133
import { WebLLMProvider, useWebLLM } from "./WebLLMProvider";
3234
import * as webllm from "@mlc-ai/web-llm";
35+
import Link from "next/link";
3336

3437
interface ChatComponentProps {
3538
isOpen?: boolean;
3639
onClose?: () => void;
40+
isFullPage?: boolean;
3741
}
3842

3943
export default function ChatComponent(props: ChatComponentProps) {
@@ -47,8 +51,9 @@ export default function ChatComponent(props: ChatComponentProps) {
4751
function ChatComponentInner({
4852
isOpen: controlledIsOpen,
4953
onClose,
54+
isFullPage = false,
5055
}: ChatComponentProps) {
51-
const [isOpen, setIsOpen] = useState<boolean>(!!controlledIsOpen);
56+
const [isOpen, setIsOpen] = useState<boolean>(isFullPage || !!controlledIsOpen);
5257
const [model, setModel] = useState("gpt-4o");
5358
const [availableModels, setAvailableModels] = useState<{ id: string; name: string; isLocal?: boolean }[]>([]);
5459
const [input, setInput] = useState("");
@@ -331,15 +336,28 @@ function ChatComponentInner({
331336

332337
return (
333338
<Card
334-
className="fixed bottom-0 right-0 z-[60] w-full max-w-sm sm:max-w-md md:max-w-md h-[70vh] sm:h-[80vh] flex flex-col bg-zinc-900 border border-zinc-800 shadow-2xl rounded-tl-xl p-1 gap-0"
335-
style={{ boxShadow: "0 8px 32px 0 rgba(0,0,0,0.45)" }}
339+
className={cn(
340+
"z-[60] flex flex-col bg-zinc-900 border-zinc-800 shadow-2xl p-1 gap-0",
341+
isFullPage
342+
? "w-full h-full rounded-xl border-zinc-700/50"
343+
: "fixed bottom-0 right-0 w-full max-w-sm sm:max-w-md md:max-w-md h-[70vh] sm:h-[80vh] rounded-tl-xl"
344+
)}
345+
style={!isFullPage ? { boxShadow: "0 8px 32px 0 rgba(0,0,0,0.45)" } : {}}
336346
>
337347
{/* Header */}
338-
<div className="flex items-center justify-between px-4 py-3 border-b border-zinc-800 bg-zinc-950 rounded-tl-xl">
348+
<div className={cn(
349+
"flex items-center justify-between px-4 py-3 border-b border-zinc-800 bg-zinc-950",
350+
isFullPage ? "rounded-t-xl" : "rounded-tl-xl"
351+
)}>
339352
<div className="flex flex-col">
340353
<div className="flex items-center gap-2">
341354
<MessageCircle className="w-5 h-5 text-blue-400" />
342355
<span className="font-semibold text-white text-lg">ST-K8s Chat</span>
356+
{!isFullPage && (
357+
<Link href="/chat" target="_blank" className="ml-1 text-zinc-500 hover:text-blue-400 transition-colors">
358+
<ExternalLink className="w-3.5 h-3.5" />
359+
</Link>
360+
)}
343361
</div>
344362
<div className="flex items-center gap-2 mt-0.5">
345363
<button
@@ -444,15 +462,17 @@ function ChatComponentInner({
444462
)}
445463
</DropdownMenuContent>
446464
</DropdownMenu>
447-
<Button
448-
variant="ghost"
449-
size="icon"
450-
className="text-zinc-400 hover:text-white w-8 h-8"
451-
onClick={handleToggle}
452-
aria-label="Close chat"
453-
>
454-
<X className="w-5 h-5" />
455-
</Button>
465+
{!isFullPage && (
466+
<Button
467+
variant="ghost"
468+
size="icon"
469+
className="text-zinc-400 hover:text-white w-8 h-8"
470+
onClick={handleToggle}
471+
aria-label="Close chat"
472+
>
473+
<X className="w-5 h-5" />
474+
</Button>
475+
)}
456476
</div>
457477
</div>
458478

@@ -556,10 +576,13 @@ function ChatComponentInner({
556576
className={`flex \${msg.role === "user" ? "justify-end" : "justify-start"}`}
557577
>
558578
<div
559-
className={`max-w-[80%] px-4 py-2 rounded-lg text-sm whitespace-pre-line \${msg.role === "user"
560-
? "bg-blue-600 text-white rounded-br-none"
561-
: "bg-zinc-800 text-zinc-100 rounded-bl-none border border-zinc-700"
562-
}`}
579+
className={cn(
580+
"px-4 py-2 rounded-lg text-sm whitespace-pre-line",
581+
isFullPage ? "max-w-[90%]" : "max-w-[80%]",
582+
msg.role === "user"
583+
? "bg-blue-600 text-white rounded-br-none"
584+
: "bg-zinc-800 text-zinc-100 rounded-bl-none border border-zinc-700"
585+
)}
563586
>
564587
{msg.content}
565588
</div>
@@ -575,7 +598,10 @@ function ChatComponentInner({
575598
)}
576599
{webllmLoading && webllmProgress && (
577600
<div className="flex justify-start">
578-
<div className="flex flex-col gap-1 bg-zinc-800 text-zinc-300 px-4 py-2 rounded-lg border border-zinc-700 w-full max-w-[80%]">
601+
<div className={cn(
602+
"flex flex-col gap-1 bg-zinc-800 text-zinc-300 px-4 py-2 rounded-lg border border-zinc-700 w-full",
603+
isFullPage ? "max-w-[90%]" : "max-w-[80%]"
604+
)}>
579605
<div className="flex justify-between text-xs mb-1">
580606
<span>Downloading Model Array into Browser</span>
581607
</div>

0 commit comments

Comments
 (0)