better chat ui

This commit is contained in:
2026-02-22 20:52:19 +00:00
parent f1e3ce4b76
commit 7fe7260f51
7 changed files with 731 additions and 322 deletions
@@ -22,7 +22,7 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => {
useEffect(() => {
if (isNew) {
setSelected({ id: 'new' });
setSelected({ id: `new:${Date.now()}` });
return;
}
if (!sessionId) return;
File diff suppressed because it is too large Load Diff
+89
View File
@@ -152,3 +152,92 @@ export type ModelInfo = {
reasoning?: boolean;
images?: boolean;
};
// ── Pi-native JSONL types ──────────────────────────────────────────────
export type JnlSessionHeader = {
type: 'session';
version: 3;
id: string;
timestamp: string;
cwd: string;
};
export type JnlEntryBase = {
type: string;
id: string;
parentId?: string;
timestamp: string;
};
export type JnlTextContent = {
type: 'text';
text: string;
};
export type JnlToolCall = {
type: 'tool_use';
id: string;
name: string;
input: Record<string, unknown>;
};
export type JnlUserMessage = JnlEntryBase & {
type: 'message';
message: {
role: 'user';
content: string;
};
};
export type JnlAssistantMessage = JnlEntryBase & {
type: 'message';
message: {
role: 'assistant';
content: Array<JnlTextContent | JnlToolCall>;
};
};
export type JnlToolResultMessage = JnlEntryBase & {
type: 'message';
message: {
role: 'toolResult';
toolCallId: string;
toolName: string;
content: Array<JnlTextContent>;
isError?: boolean;
};
};
export type JnlMessageEntry = JnlUserMessage | JnlAssistantMessage | JnlToolResultMessage;
export type JnlSessionInfoEntry = JnlEntryBase & {
type: 'session_info';
name: string;
officer?: {
cost: MessageCost;
model: string;
groupSlug?: string | null;
messageCount: number;
createdAt: number;
updatedAt: number;
};
};
export type JnlEntry = JnlMessageEntry | JnlSessionInfoEntry;
// ── Session Index ──────────────────────────────────────────────────────
export type SessionIndexEntry = {
file: string;
title: string;
model: string;
cwd: string;
createdAt: number;
updatedAt: number;
messageCount: number;
cost: MessageCost;
groupSlug?: string | null;
};
export type SessionIndex = Record<string, SessionIndexEntry>;
@@ -1,4 +1,3 @@
import { useEffect } from 'react';
import { useLocation } from 'react-router';
import { Trash2 } from 'lucide-react';
import { usePanelChannel } from 'hooks/usePanelChannel';
@@ -83,6 +82,7 @@ function SessionChat({ sessionId, model }: SessionChatProps) {
}}
/>
<EmbeddableChat
chat={chat}
sessionId={sessionId}
initialModel={model ?? undefined}
className="flex-1 min-h-0"
@@ -94,15 +94,8 @@ function SessionChat({ sessionId, model }: SessionChatProps) {
function NewChat() {
const location = useLocation();
const locationState = location.state as ChatLocationState;
const [, setSelected] = usePanelChannel<SelectedSession>(CHANNEL, null);
const chat = usePiChat();
useEffect(() => {
if (chat.sessionId) {
setSelected({ id: chat.sessionId, model: chat.model });
}
}, [chat.sessionId]);
const chat = usePiChat(undefined, locationState?.model);
const initialMessage = locationState?.initialMessage
? {
@@ -122,6 +115,7 @@ function NewChat() {
onDelete={undefined}
/>
<EmbeddableChat
chat={chat}
sessionId={undefined}
initialModel={locationState?.model ?? undefined}
initialMessage={initialMessage}
@@ -133,16 +127,6 @@ function NewChat() {
);
}
function NewChatPanel() {
const [selected] = usePanelChannel<SelectedSession>(CHANNEL, null);
if (selected && selected.id !== 'new') {
return <SessionChat key={selected.id} sessionId={selected.id} model={selected.model} />;
}
return <NewChat key="new" />;
}
export const ChatDetailPanel = () => {
const [selected] = usePanelChannel<SelectedSession>(CHANNEL, null);
@@ -154,8 +138,8 @@ export const ChatDetailPanel = () => {
);
}
if (selected.id === 'new') {
return <NewChatPanel key="new" />;
if (selected.id.startsWith('new')) {
return <NewChat key={selected.id} />;
}
return <SessionChat key={selected.id} sessionId={selected.id} model={selected.model} />;
@@ -132,7 +132,7 @@ export const SessionList = () => {
</button>
<button
onClick={() => {
setSelected({ id: 'new' });
setSelected({ id: `new:${Date.now()}` });
window.history.replaceState(null, '', '/chat/new');
}}
className="flex items-center gap-1.5 rounded-md bg-duck-teal hover:bg-duck-teal/90 text-duck-yellow cursor-pointer h-7 px-3 text-xs font-medium"
@@ -46,7 +46,7 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
const sessionIdRef = useRef<string | null>(initialSessionId ?? null);
const saveTimerRef = useRef<number | null>(null);
const { getSession, saveMessages } = useChatSessions();
const { getSession, saveMessages, invalidate: invalidateSessions } = useChatSessions();
const token = localStorage.getItem('BEARER_TOKEN');
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -124,6 +124,7 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
},
]);
setIsGenerating(false);
invalidateSessions();
break;
case 'sync:messages':
@@ -148,6 +149,7 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
return { role: 'assistant', text: '' }; // Fallback
});
setMessages(chatMessages);
if (chatMessages.length > 0) setHasStarted(true);
setIsGenerating(msg.isGenerating);
if (msg.streamingText) {
streamingRef.current = msg.streamingText;
@@ -180,7 +182,10 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
sessionIdRef.current = sid;
setSessionId(sid);
}
if (msgs.length > 0) setMessages(msgs);
if (msgs.length > 0) {
setMessages(msgs);
setHasStarted(true);
}
})
.catch(() => {});
return;
@@ -208,6 +213,7 @@ export function usePiChat(initialSessionId?: string, initialModel?: string | nul
return { role: 'assistant', text: '' }; // Fallback
});
setMessages(chatMessages);
setHasStarted(true);
}
})
.catch(() => {});
@@ -39,6 +39,10 @@ export function useChatSessions() {
return client.get<{ results: SessionEntry[] }>(`/pi/sessions/search?q=${encodeURIComponent(query)}`);
}
function invalidate() {
queryClient.invalidateQueries({ queryKey: ['PI_SESSIONS'] });
}
return {
sessions,
getSession,
@@ -46,6 +50,7 @@ export function useChatSessions() {
renameSession,
deleteSession,
searchSessions,
invalidate,
};
}