Quick chat FAB, active session indicators, memory tab counts, improved new session
This commit is contained in:
@@ -4,9 +4,11 @@ import { useState } from "react";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { ArrowLeft, Send, Loader2 } from "lucide-react";
|
import { ArrowLeft, Send, Loader2 } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useToast } from "@/components/Toast";
|
||||||
|
|
||||||
export default function NewSessionPage() {
|
export default function NewSessionPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
|
|
||||||
@@ -14,7 +16,6 @@ export default function NewSessionPage() {
|
|||||||
if (!message.trim() || sending) return;
|
if (!message.trim() || sending) return;
|
||||||
setSending(true);
|
setSending(true);
|
||||||
try {
|
try {
|
||||||
// Create session
|
|
||||||
const res = await fetch("/api/sessions", {
|
const res = await fetch("/api/sessions", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -24,7 +25,6 @@ export default function NewSessionPage() {
|
|||||||
const sessionId = data.session?.id;
|
const sessionId = data.session?.id;
|
||||||
if (!sessionId) throw new Error("No session ID");
|
if (!sessionId) throw new Error("No session ID");
|
||||||
|
|
||||||
// Send first message
|
|
||||||
await fetch("/api/sessions/" + sessionId + "/chat", {
|
await fetch("/api/sessions/" + sessionId + "/chat", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -32,7 +32,8 @@ export default function NewSessionPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.push("/history/" + sessionId);
|
router.push("/history/" + sessionId);
|
||||||
} catch {
|
} catch (e) {
|
||||||
|
toast("Failed to create session", "error");
|
||||||
setSending(false);
|
setSending(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -46,11 +47,15 @@ export default function NewSessionPage() {
|
|||||||
<h1 className="text-[15px] font-medium" style={{ color: "var(--text-1)" }}>New Session</h1>
|
<h1 className="text-[15px] font-medium" style={{ color: "var(--text-1)" }}>New Session</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p className="text-[12px]" style={{ color: "var(--text-3)" }}>
|
||||||
|
Creates a new web session and sends your first message.
|
||||||
|
</p>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
placeholder="What do you need?"
|
placeholder="What do you need?"
|
||||||
className="w-full min-h-[120px] p-4 rounded-2xl text-[14px] resize-none outline-none transition-colors"
|
className="w-full min-h-[140px] p-4 rounded-2xl text-[14px] resize-none outline-none transition-colors leading-relaxed"
|
||||||
style={{
|
style={{
|
||||||
background: "var(--bg-raised)",
|
background: "var(--bg-raised)",
|
||||||
border: "1px solid var(--border)",
|
border: "1px solid var(--border)",
|
||||||
@@ -58,6 +63,7 @@ export default function NewSessionPage() {
|
|||||||
}}
|
}}
|
||||||
autoFocus
|
autoFocus
|
||||||
disabled={sending}
|
disabled={sending}
|
||||||
|
onKeyDown={(e) => { if (e.key === "Enter" && e.metaKey) create(); }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -71,6 +77,12 @@ export default function NewSessionPage() {
|
|||||||
<><Send size={16} /> Send</>
|
<><Send size={16} /> Send</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{sending && (
|
||||||
|
<p className="text-[12px] text-center" style={{ color: "var(--text-3)" }}>
|
||||||
|
This may take a moment — Hermes is processing your message.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-17
@@ -6,10 +6,7 @@ import { useMemory, useAddMemory, usePatchMemory, useDeleteMemory } from "@/lib/
|
|||||||
import { MemoryEntry } from "@/components/MemoryEntry";
|
import { MemoryEntry } from "@/components/MemoryEntry";
|
||||||
import { useToast } from "@/components/Toast";
|
import { useToast } from "@/components/Toast";
|
||||||
|
|
||||||
const TABS = [
|
const TAB_IDS = ["memory", "user"] as const;
|
||||||
{ id: "memory", label: "Agent" },
|
|
||||||
{ id: "user", label: "User" },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export default function MemoryPage() {
|
export default function MemoryPage() {
|
||||||
const [tab, setTab] = useState<"memory" | "user">("memory");
|
const [tab, setTab] = useState<"memory" | "user">("memory");
|
||||||
@@ -63,19 +60,26 @@ export default function MemoryPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-1 p-1 rounded-xl" style={{ background: "var(--bg-raised)" }}>
|
<div className="flex gap-1 p-1 rounded-xl" style={{ background: "var(--bg-raised)" }}>
|
||||||
{TABS.map((t) => (
|
{TAB_IDS.map((id) => {
|
||||||
<button
|
const t = data?.targets?.find((x) => x.target === id);
|
||||||
key={t.id}
|
const label = id === "memory" ? "Agent" : "User";
|
||||||
onClick={() => setTab(t.id)}
|
return (
|
||||||
className="flex-1 py-2 rounded-lg text-sm font-medium transition-all"
|
<button
|
||||||
style={{
|
key={id}
|
||||||
background: tab === t.id ? "var(--bg-hover)" : "transparent",
|
onClick={() => setTab(id)}
|
||||||
color: tab === t.id ? "var(--text-1)" : "var(--text-3)",
|
className="flex-1 py-2 rounded-lg text-sm font-medium transition-all flex items-center justify-center gap-1.5"
|
||||||
}}
|
style={{
|
||||||
>
|
background: tab === id ? "var(--bg-hover)" : "transparent",
|
||||||
{t.label}
|
color: tab === id ? "var(--text-1)" : "var(--text-3)",
|
||||||
</button>
|
}}
|
||||||
))}
|
>
|
||||||
|
{label}
|
||||||
|
{t && (
|
||||||
|
<span className="text-[10px] opacity-50">{t.entry_count}</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{usage && (
|
{usage && (
|
||||||
|
|||||||
+10
-1
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Puzzle, Timer, Sparkles, Settings } from "lucide-react";
|
import { Puzzle, Timer, Sparkles, Settings, MessageCircle } from "lucide-react";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { StatusCard } from "@/components/StatusCard";
|
import { StatusCard } from "@/components/StatusCard";
|
||||||
import { StatsCard } from "@/components/StatsCard";
|
import { StatsCard } from "@/components/StatsCard";
|
||||||
@@ -66,6 +66,15 @@ export default function PulsePage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Quick chat FAB */}
|
||||||
|
<Link
|
||||||
|
href="/history/new"
|
||||||
|
className="fixed right-5 z-40 flex items-center justify-center w-14 h-14 rounded-full shadow-lg active:scale-90 transition-transform"
|
||||||
|
style={{ background: "var(--accent)", bottom: "calc(5.5rem + var(--safe-bottom))" }}
|
||||||
|
>
|
||||||
|
<MessageCircle size={22} color="white" />
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export function SessionCard({ session }: { session: Session }) {
|
|||||||
${cost.toFixed(2)}
|
${cost.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-[11px] ml-auto" style={{ color: "var(--text-3)" }}>
|
<span className="text-[11px] ml-auto" style={{ color: !session.ended_at ? "var(--accent)" : "var(--text-3)" }}>
|
||||||
{timeAgo(session.started_at)}
|
{timeAgo(session.started_at, !session.ended_at)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
export function timeAgo(ts: number): string {
|
export function timeAgo(ts: number, active?: boolean): string {
|
||||||
|
if (active) return "now";
|
||||||
const now = Date.now() / 1000;
|
const now = Date.now() / 1000;
|
||||||
const diff = now - ts;
|
const diff = now - ts;
|
||||||
if (diff < 60) return "just now";
|
if (diff < 60) return "just now";
|
||||||
|
|||||||
Reference in New Issue
Block a user