harness uniformization

This commit is contained in:
2026-02-17 20:37:40 +00:00
parent 74e98e95e1
commit 3733e99ba8
17 changed files with 338 additions and 356 deletions
@@ -1,6 +1,8 @@
import { useRef, useEffect, useState } from 'react';
import { useNavigate, useLocation } from 'react-router';
import { useSessions, useOpenCodeSessions, useSlashCommands, SessionBar } from 'widgets/ChatHistory';
import { useChatSessions } from '@/state/useChatSessions';
import { useSlashCommands } from '@/state/useSlashCommands';
import { SessionBar } from 'widgets/ChatHistory';
import type { ModelOption } from '@/state/useModels';
import type { useClaude } from './useClaude';
import { EmbeddableChat, type Attachment } from './EmbeddableChat';
@@ -24,13 +26,7 @@ export const ChatPanel = ({ chat, provider = 'claude', availableModels = [], onP
const [fullscreen, setFullscreen] = useState(false);
const initialSentRef = useRef(false);
const claudeSessions = useSessions();
const opencodeSessions = useOpenCodeSessions();
const { archiveSession, deleteSession } =
provider === 'claude'
? claudeSessions
: { archiveSession: undefined, deleteSession: opencodeSessions.deleteSession };
const sessions = provider === 'claude' ? claudeSessions.sessions : opencodeSessions.sessions;
const { sessions, archiveSession, deleteSession } = useChatSessions();
const slashCommands = useSlashCommands({ sessionId });
const sessionTitle = sessionId ? sessions.find((s) => s.id === sessionId)?.title : undefined;
const listPath = '/chat';
@@ -89,16 +85,16 @@ export const ChatPanel = ({ chat, provider = 'claude', availableModels = [], onP
isGenerating={isGenerating}
fullscreen={fullscreen}
onArchive={
archiveSession && sessionId
provider === 'claude' && sessionId
? async () => {
await archiveSession(sessionId);
await archiveSession(provider, sessionId);
navigate(listPath);
}
: undefined
}
onDelete={async () => {
if (!sessionId) return;
await deleteSession(sessionId);
await deleteSession(provider, sessionId);
navigate(listPath);
}}
onToggleFullscreen={() => setFullscreen((f) => !f)}
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { useChatWebSocket } from 'hooks/useChatWebSocket';
import { useSessions } from 'widgets/ChatHistory';
import { useChatSessions } from '@/state/useChatSessions';
import type { ChatMessage, ServerMessage, TaskInfo } from 'widgets/Chat';
const SAVE_DEBOUNCE_MS = 1000;
@@ -31,7 +31,7 @@ export const useClaude = (initialSessionId?: string, initialModel?: string | nul
const sessionIdRef = useRef<string | null>(initialSessionId ?? null);
const saveTimerRef = useRef<number | null>(null);
const { getMessages, saveMessages } = useSessions();
const { getMessages, saveMessages } = useChatSessions();
const token = localStorage.getItem('BEARER_TOKEN');
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
@@ -137,7 +137,7 @@ export const useClaude = (initialSessionId?: string, initialModel?: string | nul
return;
}
if (!initialSessionId) return;
getMessages(initialSessionId)
getMessages('claude', initialSessionId)
.then((data) => {
if (Array.isArray(data) && data.length > 0) setMessages(data);
})
@@ -156,7 +156,7 @@ export const useClaude = (initialSessionId?: string, initialModel?: string | nul
if (storage) {
storage.save(sid, snapshot).catch(() => {});
} else {
saveMessages(sid, snapshot).catch(() => {});
saveMessages('claude', sid, snapshot).catch(() => {});
}
saveTimerRef.current = null;
}, SAVE_DEBOUNCE_MS);
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from 'react';
import { useChatWebSocket } from 'hooks/useChatWebSocket';
import { useSettings } from '@/state/useSettings';
import { useVisibleOpenCodeModels } from '@/state/useModels';
import { useOpenCodeSessions } from 'widgets/ChatHistory';
import { useChatSessions } from '@/state/useChatSessions';
import type { ChatMessage, ServerMessage, TaskInfo } from 'widgets/Chat';
type UseOpenCodeOptions = {
@@ -29,7 +29,7 @@ export const useOpenCode = (initialSessionId?: string, initialModel?: string | n
setSelectedModel(value);
};
const { getMessages } = useOpenCodeSessions();
const { getMessages } = useChatSessions();
const { settings } = useSettings();
const openCodeModels = useVisibleOpenCodeModels();
@@ -144,7 +144,7 @@ export const useOpenCode = (initialSessionId?: string, initialModel?: string | n
// Load messages from OpenCode on mount when resuming a session
useEffect(() => {
if (!initialSessionId) return;
getMessages(initialSessionId)
getMessages('opencode', initialSessionId)
.then((data) => {
if (Array.isArray(data) && data.length > 0) setMessages(data);
})
@@ -1,28 +1,17 @@
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { Link } from 'react-router';
import { Plus, MessageSquare, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/Card';
import { useSessions, useOpenCodeSessions } from 'widgets/ChatHistory';
import { useChatSessions } from '@/state/useChatSessions';
type Filter = 'all' | 'claude' | 'opencode';
export const SessionList = () => {
const [filter, setFilter] = useState<Filter>('all');
const claude = useSessions();
const opencode = useOpenCodeSessions();
const { sessions, deleteSession } = useChatSessions();
const merged = useMemo(
() => [...claude.sessions, ...opencode.sessions].sort((a, b) => b.createdAt - a.createdAt),
[claude.sessions, opencode.sessions],
);
const filtered = filter === 'all' ? merged : merged.filter((s) => s.provider === filter);
const handleDelete = (id: string, provider: 'claude' | 'opencode') => {
if (provider === 'claude') claude.deleteSession(id);
else opencode.deleteSession(id);
};
const filtered = filter === 'all' ? sessions : sessions.filter((s) => s.provider === filter);
return (
<div className="flex flex-col h-full items-center p-4 md:p-6">
@@ -90,7 +79,7 @@ export const SessionList = () => {
</div>
</Link>
<button
onClick={() => handleDelete(session.id, session.provider)}
onClick={() => deleteSession(session.provider, session.id)}
className="shrink-0 p-2 mr-2 text-duck-dark/20 hover:text-red-500 md:opacity-0 md:group-hover:opacity-100 transition-opacity cursor-pointer"
>
<Trash2 className="h-4 w-4" />
@@ -1,25 +1,12 @@
import { useMemo } from 'react';
import { Link } from 'react-router';
import { MessageSquare, Trash2, ChevronDown, ChevronUp } from 'lucide-react';
import { Card } from '@/components/Card';
import { useSessions } from 'widgets/ChatHistory';
import { useOpenCodeSessions } from 'widgets/ChatHistory';
import { useChatSessions } from '@/state/useChatSessions';
import { useUserState } from '@/state/useUserState';
export const ChatHistory = () => {
const [collapsed, setCollapsed] = useUserState('widget:chatHistory:collapsed', true);
const claude = useSessions();
const opencode = useOpenCodeSessions();
const sessions = useMemo(
() => [...claude.sessions, ...opencode.sessions].sort((a, b) => b.createdAt - a.createdAt),
[claude.sessions, opencode.sessions],
);
const handleDelete = (id: string, provider: 'claude' | 'opencode') => {
if (provider === 'claude') claude.deleteSession(id);
else opencode.deleteSession(id);
};
const { sessions, deleteSession } = useChatSessions();
return (
<div className="w-full">
@@ -72,7 +59,7 @@ export const ChatHistory = () => {
</div>
</Link>
<button
onClick={() => handleDelete(session.id, session.provider)}
onClick={() => deleteSession(session.provider, session.id)}
className="shrink-0 p-1 rounded text-duck-dark/20 md:opacity-0 md:group-hover:opacity-100 hover:text-red-500 transition-opacity cursor-pointer"
>
<Trash2 className="h-3.5 w-3.5" />