Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed
- [ask sb] Fixed "413 content too large" error when starting a new chat with many repos selected. [#416](https://github.com/sourcebot-dev/sourcebot/pull/416)

## [4.6.1] - 2025-07-29

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import { ResizablePanel } from '@/components/ui/resizable';
import { ChatThread } from '@/features/chat/components/chatThread';
import { LanguageModelInfo, SBChatMessage, SearchScope, SET_CHAT_STATE_QUERY_PARAM, SetChatStatePayload } from '@/features/chat/types';
import { LanguageModelInfo, SBChatMessage, SearchScope, SET_CHAT_STATE_SESSION_STORAGE_KEY, SetChatStatePayload } from '@/features/chat/types';
import { RepositoryQuery, SearchContextQuery } from '@/lib/types';
import { CreateUIMessage } from 'ai';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useChatId } from '../../useChatId';
import { useSessionStorage } from 'usehooks-ts';

interface ChatThreadPanelProps {
languageModels: LanguageModelInfo[];
Expand All @@ -29,34 +29,29 @@ export const ChatThreadPanel = ({
// @note: we are guaranteed to have a chatId because this component will only be
// mounted when on a /chat/[id] route.
const chatId = useChatId()!;
const router = useRouter();
const searchParams = useSearchParams();
const [inputMessage, setInputMessage] = useState<CreateUIMessage<SBChatMessage> | undefined>(undefined);
const [chatState, setChatState] = useSessionStorage<SetChatStatePayload | null>(SET_CHAT_STATE_SESSION_STORAGE_KEY, null);

// Use the last user's last message to determine what repos and contexts we should select by default.
const lastUserMessage = messages.findLast((message) => message.role === "user");
const defaultSelectedSearchScopes = lastUserMessage?.metadata?.selectedSearchScopes ?? [];
const [selectedSearchScopes, setSelectedSearchScopes] = useState<SearchScope[]>(defaultSelectedSearchScopes);

useEffect(() => {
const setChatState = searchParams.get(SET_CHAT_STATE_QUERY_PARAM);
if (!setChatState) {
if (!chatState) {
return;
}

try {
const { inputMessage, selectedSearchScopes } = JSON.parse(setChatState) as SetChatStatePayload;
setInputMessage(inputMessage);
setSelectedSearchScopes(selectedSearchScopes);
setInputMessage(chatState.inputMessage);
setSelectedSearchScopes(chatState.selectedSearchScopes);
} catch {
console.error('Invalid message in URL');
console.error('Invalid chat state in session storage');
} finally {
setChatState(null);
}

// Remove the message from the URL
const newSearchParams = new URLSearchParams(searchParams.toString());
newSearchParams.delete(SET_CHAT_STATE_QUERY_PARAM);
router.replace(`?${newSearchParams.toString()}`, { scroll: false });
}, [searchParams, router]);
}, [chatState, setChatState]);

return (
<ResizablePanel
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/features/chat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ declare module 'slate' {
/////////////////////////

// Misc //
export const SET_CHAT_STATE_QUERY_PARAM = 'setChatState';
export const SET_CHAT_STATE_SESSION_STORAGE_KEY = 'setChatState';

export type SetChatStatePayload = {
inputMessage: CreateUIMessage<SBChatMessage>;
Expand Down
20 changes: 12 additions & 8 deletions packages/web/src/features/chat/useCreateNewChatThread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ import { useRouter } from "next/navigation";
import { createChat } from "./actions";
import { isServiceError } from "@/lib/utils";
import { createPathWithQueryParams } from "@/lib/utils";
import { SearchScope, SET_CHAT_STATE_QUERY_PARAM, SetChatStatePayload } from "./types";
import { SearchScope, SET_CHAT_STATE_SESSION_STORAGE_KEY, SetChatStatePayload } from "./types";
import { useSessionStorage } from "usehooks-ts";

export const useCreateNewChatThread = () => {
const domain = useDomain();
const [isLoading, setIsLoading] = useState(false);
const { toast } = useToast();
const router = useRouter();

const [, setChatState] = useSessionStorage<SetChatStatePayload | null>(SET_CHAT_STATE_SESSION_STORAGE_KEY, null);


const createNewChatThread = useCallback(async (children: Descendant[], selectedSearchScopes: SearchScope[]) => {
const text = slateContentToString(children);
const mentions = getAllMentionElements(children);
Expand All @@ -34,16 +38,16 @@ export const useCreateNewChatThread = () => {
return;
}

const url = createPathWithQueryParams(`/${domain}/chat/${response.id}`,
[SET_CHAT_STATE_QUERY_PARAM, JSON.stringify({
inputMessage,
selectedSearchScopes,
} satisfies SetChatStatePayload)],
);
setChatState({
inputMessage,
selectedSearchScopes,
});

const url = createPathWithQueryParams(`/${domain}/chat/${response.id}`);

router.push(url);
router.refresh();
}, [domain, router, toast]);
}, [domain, router, toast, setChatState]);

return {
createNewChatThread,
Expand Down