introduces workspace items as combined search+chat model
This commit is contained in:
101
web/src/App.tsx
101
web/src/App.tsx
@@ -20,8 +20,7 @@ import {
|
||||
getChat,
|
||||
listModels,
|
||||
getSearch,
|
||||
listChats,
|
||||
listSearches,
|
||||
listWorkspaceItems,
|
||||
runCompletionStream,
|
||||
runSearchStream,
|
||||
suggestChatTitle,
|
||||
@@ -37,6 +36,7 @@ import {
|
||||
type SearchDetail,
|
||||
type SearchSummary,
|
||||
type ToolCallEvent,
|
||||
type WorkspaceItem,
|
||||
} from "@/lib/api";
|
||||
import { useSessionAuth } from "@/hooks/use-session-auth";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -588,20 +588,48 @@ function getSearchTitle(search: Pick<SearchSummary, "title" | "query">) {
|
||||
return "New search";
|
||||
}
|
||||
|
||||
function buildSidebarItems(chats: ChatSummary[], searches: SearchSummary[]): SidebarItem[] {
|
||||
const items: SidebarItem[] = [
|
||||
...chats.map((chat) => ({
|
||||
kind: "chat" as const,
|
||||
id: chat.id,
|
||||
title: getChatTitle(chat),
|
||||
updatedAt: chat.updatedAt,
|
||||
createdAt: chat.createdAt,
|
||||
initiatedProvider: chat.initiatedProvider,
|
||||
initiatedModel: chat.initiatedModel,
|
||||
lastUsedProvider: chat.lastUsedProvider,
|
||||
lastUsedModel: chat.lastUsedModel,
|
||||
})),
|
||||
...searches.map((search) => ({
|
||||
function chatWorkspaceItem(chat: ChatSummary): WorkspaceItem {
|
||||
return { type: "chat", ...chat };
|
||||
}
|
||||
|
||||
function searchWorkspaceItem(search: SearchSummary): WorkspaceItem {
|
||||
return { type: "search", ...search };
|
||||
}
|
||||
|
||||
function splitWorkspaceItems(items: WorkspaceItem[]) {
|
||||
const chats: ChatSummary[] = [];
|
||||
const searches: SearchSummary[] = [];
|
||||
for (const item of items) {
|
||||
if (item.type === "chat") {
|
||||
const { type: _type, ...chat } = item;
|
||||
chats.push(chat);
|
||||
} else {
|
||||
const { type: _type, ...search } = item;
|
||||
searches.push(search);
|
||||
}
|
||||
}
|
||||
return { chats, searches };
|
||||
}
|
||||
|
||||
function buildSidebarItems(items: WorkspaceItem[]): SidebarItem[] {
|
||||
return items.map((item) => {
|
||||
if (item.type === "chat") {
|
||||
const chat = item;
|
||||
return {
|
||||
kind: "chat" as const,
|
||||
id: chat.id,
|
||||
title: getChatTitle(chat),
|
||||
updatedAt: chat.updatedAt,
|
||||
createdAt: chat.createdAt,
|
||||
initiatedProvider: chat.initiatedProvider,
|
||||
initiatedModel: chat.initiatedModel,
|
||||
lastUsedProvider: chat.lastUsedProvider,
|
||||
lastUsedModel: chat.lastUsedModel,
|
||||
};
|
||||
}
|
||||
|
||||
const search = item;
|
||||
return {
|
||||
kind: "search" as const,
|
||||
id: search.id,
|
||||
title: getSearchTitle(search),
|
||||
@@ -611,10 +639,21 @@ function buildSidebarItems(chats: ChatSummary[], searches: SearchSummary[]): Sid
|
||||
initiatedModel: null,
|
||||
lastUsedProvider: null,
|
||||
lastUsedModel: null,
|
||||
})),
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return items.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
||||
function upsertWorkspaceItem(items: WorkspaceItem[], item: WorkspaceItem, moveToFront = true) {
|
||||
const withoutExisting = items.filter((existing) => existing.type !== item.type || existing.id !== item.id);
|
||||
if (moveToFront) {
|
||||
return [item, ...withoutExisting];
|
||||
}
|
||||
|
||||
const existingIndex = items.findIndex((existing) => existing.type === item.type && existing.id === item.id);
|
||||
if (existingIndex < 0) return [item, ...items];
|
||||
const next = [...items];
|
||||
next[existingIndex] = item;
|
||||
return next;
|
||||
}
|
||||
|
||||
function buildActiveRunsState(activeRuns: ActiveRunsResponse): ActiveRunsState {
|
||||
@@ -675,6 +714,7 @@ export default function App() {
|
||||
|
||||
const [chats, setChats] = useState<ChatSummary[]>([]);
|
||||
const [searches, setSearches] = useState<SearchSummary[]>([]);
|
||||
const [workspaceItems, setWorkspaceItems] = useState<WorkspaceItem[]>([]);
|
||||
const [selectedItem, setSelectedItem] = useState<SidebarSelection | null>(null);
|
||||
const [selectedChat, setSelectedChat] = useState<ChatDetail | null>(null);
|
||||
const [selectedSearch, setSelectedSearch] = useState<SearchDetail | null>(null);
|
||||
@@ -801,7 +841,7 @@ export default function App() {
|
||||
pendingAttachmentsRef.current = pendingAttachments;
|
||||
}, [pendingAttachments]);
|
||||
|
||||
const sidebarItems = useMemo(() => buildSidebarItems(chats, searches), [chats, searches]);
|
||||
const sidebarItems = useMemo(() => buildSidebarItems(workspaceItems), [workspaceItems]);
|
||||
const filteredSidebarItems = useMemo(() => {
|
||||
const query = sidebarQuery.trim().toLowerCase();
|
||||
if (!query) return sidebarItems;
|
||||
@@ -817,6 +857,7 @@ export default function App() {
|
||||
const resetWorkspaceState = () => {
|
||||
setChats([]);
|
||||
setSearches([]);
|
||||
setWorkspaceItems([]);
|
||||
setSelectedItem(null);
|
||||
setSelectedChat(null);
|
||||
setSelectedSearch(null);
|
||||
@@ -852,15 +893,16 @@ export default function App() {
|
||||
const refreshCollections = async (preferredSelection?: SidebarSelection) => {
|
||||
setIsLoadingCollections(true);
|
||||
try {
|
||||
const [nextChats, nextSearches] = await Promise.all([listChats(), listSearches()]);
|
||||
const nextItems = buildSidebarItems(nextChats, nextSearches);
|
||||
const nextWorkspaceItems = await listWorkspaceItems();
|
||||
const { chats: nextChats, searches: nextSearches } = splitWorkspaceItems(nextWorkspaceItems);
|
||||
setWorkspaceItems(nextWorkspaceItems);
|
||||
setChats(nextChats);
|
||||
setSearches(nextSearches);
|
||||
|
||||
setSelectedItem((current) => {
|
||||
const hasItem = (candidate: SidebarSelection | null) => {
|
||||
if (!candidate) return false;
|
||||
return nextItems.some((item) => item.kind === candidate.kind && item.id === candidate.id);
|
||||
return nextWorkspaceItems.some((item) => item.type === candidate.kind && item.id === candidate.id);
|
||||
};
|
||||
|
||||
if (preferredSelection && hasItem(preferredSelection)) {
|
||||
@@ -869,8 +911,8 @@ export default function App() {
|
||||
if (hasItem(current)) {
|
||||
return current;
|
||||
}
|
||||
const first = nextItems[0];
|
||||
return first ? { kind: first.kind, id: first.id } : null;
|
||||
const first = nextWorkspaceItems[0];
|
||||
return first ? { kind: first.type, id: first.id } : null;
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
@@ -1551,6 +1593,7 @@ export default function App() {
|
||||
const withoutExisting = current.filter((existing) => existing.id !== chat.id);
|
||||
return [chat, ...withoutExisting];
|
||||
});
|
||||
setWorkspaceItems((current) => upsertWorkspaceItem(current, chatWorkspaceItem(chat)));
|
||||
setSelectedItem({ kind: "chat", id: chatId });
|
||||
setSelectedChat({
|
||||
id: chat.id,
|
||||
@@ -1616,6 +1659,7 @@ export default function App() {
|
||||
return { ...chat, title: updatedChat.title, updatedAt: updatedChat.updatedAt };
|
||||
})
|
||||
);
|
||||
setWorkspaceItems((current) => upsertWorkspaceItem(current, chatWorkspaceItem(updatedChat), false));
|
||||
setSelectedChat((current) => {
|
||||
if (!current || current.id !== updatedChat.id) return current;
|
||||
return { ...current, title: updatedChat.title, updatedAt: updatedChat.updatedAt };
|
||||
@@ -1748,6 +1792,11 @@ export default function App() {
|
||||
searchId = search.id;
|
||||
setDraftKind(null);
|
||||
setSelectedItem({ kind: "search", id: searchId });
|
||||
setSearches((current) => {
|
||||
const withoutExisting = current.filter((existing) => existing.id !== search.id);
|
||||
return [search, ...withoutExisting];
|
||||
});
|
||||
setWorkspaceItems((current) => upsertWorkspaceItem(current, searchWorkspaceItem(search)));
|
||||
}
|
||||
|
||||
if (!searchId) {
|
||||
@@ -2121,6 +2170,7 @@ export default function App() {
|
||||
const withoutExisting = current.filter((existing) => existing.id !== chat.id);
|
||||
return [chat, ...withoutExisting];
|
||||
});
|
||||
setWorkspaceItems((current) => upsertWorkspaceItem(current, chatWorkspaceItem(chat)));
|
||||
setSelectedItem({ kind: "chat", id: chat.id });
|
||||
setSelectedChat({
|
||||
id: chat.id,
|
||||
@@ -2296,6 +2346,7 @@ export default function App() {
|
||||
const withoutExisting = current.filter((existing) => existing.id !== chat.id);
|
||||
return [chat, ...withoutExisting];
|
||||
});
|
||||
setWorkspaceItems((current) => upsertWorkspaceItem(current, chatWorkspaceItem(chat)));
|
||||
setSelectedItem({ kind: "chat", id: chat.id });
|
||||
setSelectedChat({
|
||||
id: chat.id,
|
||||
|
||||
Reference in New Issue
Block a user