Skip to content

Commit 154c95f

Browse files
further improvements
1 parent c1467bc commit 154c95f

17 files changed

Lines changed: 285 additions & 181 deletions

File tree

packages/web/src/app/[domain]/chat/[id]/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export default async function Page(props: PageProps) {
5656
<>
5757
<TopBar
5858
domain={params.domain}
59+
homePath={`/${params.domain}/chat`}
5960
>
6061
<div className="flex flex-row gap-2 items-center">
6162
<span className="text-muted mx-2 select-none">/</span>

packages/web/src/app/[domain]/chat/components/landingPageChatBox.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { RepositoryQuery, SearchContextQuery } from "@/lib/types";
99
import { useState } from "react";
1010
import { useLocalStorage } from "usehooks-ts";
1111
import { SearchModeSelector } from "../../components/searchModeSelector";
12+
import { NotConfiguredErrorBanner } from "@/features/chat/components/notConfiguredErrorBanner";
1213

1314
interface LandingPageChatBox {
1415
languageModels: LanguageModelInfo[];
@@ -24,9 +25,13 @@ export const LandingPageChatBox = ({
2425
const { createNewChatThread, isLoading } = useCreateNewChatThread();
2526
const [selectedSearchScopes, setSelectedSearchScopes] = useLocalStorage<SearchScope[]>("selectedSearchScopes", [], { initializeWithValue: false });
2627
const [isContextSelectorOpen, setIsContextSelectorOpen] = useState(false);
28+
const isChatBoxDisabled = languageModels.length === 0;
29+
2730
return (
28-
<div className="flex flex-col items-center w-full">
29-
<div className="mt-4 w-full border rounded-md shadow-sm max-w-[800px]">
31+
<div className="w-full max-w-[800px] mt-4">
32+
33+
34+
<div className="border rounded-md w-full shadow-sm">
3035
<ChatBox
3136
onSubmit={(children) => {
3237
createNewChatThread(children, selectedSearchScopes);
@@ -37,6 +42,7 @@ export const LandingPageChatBox = ({
3742
selectedSearchScopes={selectedSearchScopes}
3843
searchContexts={searchContexts}
3944
onContextSelectorOpenChanged={setIsContextSelectorOpen}
45+
isDisabled={isChatBoxDisabled}
4046
/>
4147
<Separator />
4248
<div className="relative">
@@ -57,6 +63,10 @@ export const LandingPageChatBox = ({
5763
</div>
5864
</div>
5965
</div>
66+
67+
{isChatBoxDisabled && (
68+
<NotConfiguredErrorBanner className="mt-4" />
69+
)}
6070
</div >
6171
)
6272
}

packages/web/src/app/[domain]/components/topBar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@ import { Separator } from "@/components/ui/separator";
88
interface TopBarProps {
99
domain: string;
1010
children?: React.ReactNode;
11+
homePath?: string;
1112
}
1213

1314
export const TopBar = ({
1415
domain,
1516
children,
17+
homePath = `/${domain}`,
1618
}: TopBarProps) => {
1719
return (
1820
<div className='sticky top-0 left-0 right-0 z-10'>
1921
<div className="flex flex-row justify-between items-center py-1.5 px-3 gap-4 bg-background">
2022
<div className="grow flex flex-row gap-4 items-center">
2123
<Link
22-
href={`/${domain}`}
24+
href={homePath}
2325
className="shrink-0 cursor-pointer"
2426
>
2527
<Image

packages/web/src/app/[domain]/repos/layout.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,8 @@ export default async function Layout(
99
props: LayoutProps
1010
) {
1111
const params = await props.params;
12-
13-
const {
14-
domain
15-
} = params;
16-
17-
const {
18-
children
19-
} = props;
12+
const { domain } = params;
13+
const { children } = props;
2014

2115
return (
2216
<div className="min-h-screen flex flex-col">

packages/web/src/app/api/(server)/chat/route.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { sew, withAuth, withOrgMembership } from "@/actions";
22
import { _getConfiguredLanguageModelsFull, _getAISDKLanguageModelAndOptions, updateChatMessages } from "@/features/chat/actions";
33
import { createAgentStream } from "@/features/chat/agent";
4-
import { additionalChatRequestParamsSchema, SBChatMessage, SearchScope } from "@/features/chat/types";
5-
import { getAnswerPartFromAssistantMessage } from "@/features/chat/utils";
4+
import { additionalChatRequestParamsSchema, LanguageModelInfo, SBChatMessage, SearchScope } from "@/features/chat/types";
5+
import { getAnswerPartFromAssistantMessage, getLanguageModelKey } from "@/features/chat/utils";
66
import { ErrorCode } from "@/lib/errorCodes";
77
import { notFound, schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
88
import { isServiceError } from "@/lib/utils";
@@ -49,7 +49,11 @@ export async function POST(req: Request) {
4949
return serviceErrorResponse(schemaValidationError(parsed.error));
5050
}
5151

52-
const { messages, id, selectedSearchScopes, languageModelId } = parsed.data;
52+
const { messages, id, selectedSearchScopes, languageModel: _languageModel } = parsed.data;
53+
// @note: a bit of type massaging is required here since the
54+
// zod schema does not enum on `model` or `provider`.
55+
// @see: chat/types.ts
56+
const languageModel = _languageModel as LanguageModelInfo;
5357

5458
const response = await sew(() =>
5559
withAuth((userId) =>
@@ -78,13 +82,13 @@ export async function POST(req: Request) {
7882
// corresponding config in `config.json`.
7983
const languageModelConfig =
8084
(await _getConfiguredLanguageModelsFull())
81-
.find((model) => model.model === languageModelId);
85+
.find((model) => getLanguageModelKey(model) === getLanguageModelKey(languageModel));
8286

8387
if (!languageModelConfig) {
8488
return serviceErrorResponse({
8589
statusCode: StatusCodes.BAD_REQUEST,
8690
errorCode: ErrorCode.INVALID_REQUEST_BODY,
87-
message: `Language model ${languageModelId} is not configured.`,
91+
message: `Language model ${languageModel.model} is not configured.`,
8892
});
8993
}
9094

packages/web/src/app/globals.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
--chat-citation-border: hsl(217, 91%, 60%);
110110

111111
--warning: #ca8a04;
112+
--error: #fc5c5c;
112113
}
113114

114115
.dark {
@@ -201,6 +202,7 @@
201202
--chat-citation-border: hsl(217, 91%, 60%);
202203

203204
--warning: #fde047;
205+
--error: #f87171;
204206
}
205207
}
206208

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use client';
2+
3+
import { Button } from "@/components/ui/button";
4+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
5+
import { AtSignIcon } from "lucide-react";
6+
import { useCallback } from "react";
7+
import { ReactEditor, useSlate } from "slate-react";
8+
import { AtMentionInfoCard } from "./atMentionInfoCard";
9+
10+
// @note: we have this as a seperate component to avoid having to re-render the
11+
// entire toolbar whenever the user types (since we are using the useSlate hook
12+
// here).
13+
export const AtMentionButton = () => {
14+
const editor = useSlate();
15+
16+
const onAddContext = useCallback(() => {
17+
editor.insertText("@");
18+
ReactEditor.focus(editor);
19+
}, [editor]);
20+
21+
return (
22+
<Tooltip>
23+
<TooltipTrigger asChild>
24+
<Button
25+
variant="ghost"
26+
size="icon"
27+
className="w-6 h-6 text-muted-foreground hover:text-primary"
28+
onClick={onAddContext}
29+
>
30+
<AtSignIcon className="w-4 h-4" />
31+
</Button>
32+
</TooltipTrigger>
33+
<TooltipContent side="bottom" className="p-0 border-0 bg-transparent shadow-none">
34+
<AtMentionInfoCard />
35+
</TooltipContent>
36+
</Tooltip>
37+
);
38+
}

packages/web/src/features/chat/components/chatBox/chatBox.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface ChatBoxProps {
2727
className?: string;
2828
isRedirecting?: boolean;
2929
isGenerating?: boolean;
30+
isDisabled?: boolean;
3031
languageModels: LanguageModelInfo[];
3132
selectedSearchScopes: SearchScope[];
3233
searchContexts: SearchContextQuery[];
@@ -40,6 +41,7 @@ export const ChatBox = ({
4041
className,
4142
isRedirecting,
4243
isGenerating,
44+
isDisabled,
4345
languageModels,
4446
selectedSearchScopes,
4547
searchContexts,
@@ -68,7 +70,7 @@ export const ChatBox = ({
6870
}).flat(),
6971
});
7072
const { selectedLanguageModel } = useSelectedLanguageModel({
71-
initialLanguageModel: languageModels.length > 0 ? languageModels[0] : undefined,
73+
languageModels,
7274
});
7375
const { toast } = useToast();
7476

@@ -167,6 +169,13 @@ export const ChatBox = ({
167169
onContextSelectorOpenChanged(true);
168170
}
169171

172+
if (isSubmitDisabledReason === "no-language-model-selected") {
173+
toast({
174+
description: "⚠️ You must select a language model",
175+
variant: "destructive",
176+
});
177+
}
178+
170179
return;
171180
}
172181

@@ -287,6 +296,7 @@ export const ChatBox = ({
287296
renderElement={renderElement}
288297
renderLeaf={renderLeaf}
289298
onKeyDown={onKeyDown}
299+
readOnly={isDisabled}
290300
/>
291301
<div className="ml-auto z-10">
292302
{isRedirecting ? (
Lines changed: 18 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
'use client';
22

3-
import { Button } from "@/components/ui/button";
43
import { Separator } from "@/components/ui/separator";
5-
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
64
import { LanguageModelInfo, SearchScope } from "@/features/chat/types";
75
import { RepositoryQuery, SearchContextQuery } from "@/lib/types";
8-
import { AtSignIcon } from "lucide-react";
9-
import { useCallback } from "react";
10-
import { ReactEditor, useSlate } from "slate-react";
116
import { useSelectedLanguageModel } from "../../useSelectedLanguageModel";
7+
import { AtMentionButton } from "./atMentionButton";
128
import { LanguageModelSelector } from "./languageModelSelector";
139
import { SearchScopeSelector } from "./searchScopeSelector";
14-
import { SearchScopeInfoCard } from "@/features/chat/components/chatBox/searchScopeInfoCard";
15-
import { AtMentionInfoCard } from "@/features/chat/components/chatBox/atMentionInfoCard";
1610

1711
export interface ChatBoxToolbarProps {
1812
languageModels: LanguageModelInfo[];
@@ -33,67 +27,29 @@ export const ChatBoxToolbar = ({
3327
isContextSelectorOpen,
3428
onContextSelectorOpenChanged,
3529
}: ChatBoxToolbarProps) => {
36-
const editor = useSlate();
37-
38-
const onAddContext = useCallback(() => {
39-
editor.insertText("@");
40-
ReactEditor.focus(editor);
41-
}, [editor]);
42-
4330
const { selectedLanguageModel, setSelectedLanguageModel } = useSelectedLanguageModel({
44-
initialLanguageModel: languageModels.length > 0 ? languageModels[0] : undefined,
31+
languageModels,
4532
});
4633

4734
return (
4835
<>
49-
<Tooltip>
50-
<TooltipTrigger asChild>
51-
<Button
52-
variant="ghost"
53-
size="icon"
54-
className="w-6 h-6 text-muted-foreground hover:text-primary"
55-
onClick={onAddContext}
56-
>
57-
<AtSignIcon className="w-4 h-4" />
58-
</Button>
59-
</TooltipTrigger>
60-
<TooltipContent side="bottom" className="p-0 border-0 bg-transparent shadow-none">
61-
<AtMentionInfoCard />
62-
</TooltipContent>
63-
</Tooltip>
36+
<AtMentionButton />
6437
<Separator orientation="vertical" className="h-3 mx-1" />
65-
<Tooltip>
66-
<TooltipTrigger asChild>
67-
<SearchScopeSelector
68-
className="bg-inherit w-fit h-6 min-h-6"
69-
repos={repos}
70-
searchContexts={searchContexts}
71-
selectedSearchScopes={selectedSearchScopes}
72-
onSelectedSearchScopesChange={onSelectedSearchScopesChange}
73-
isOpen={isContextSelectorOpen}
74-
onOpenChanged={onContextSelectorOpenChanged}
75-
/>
76-
</TooltipTrigger>
77-
<TooltipContent side="bottom" className="p-0 border-0 bg-transparent shadow-none">
78-
<SearchScopeInfoCard />
79-
</TooltipContent>
80-
</Tooltip>
81-
{languageModels.length > 0 && (
82-
<>
83-
<Separator orientation="vertical" className="h-3 ml-1 mr-2" />
84-
<Tooltip>
85-
<TooltipTrigger asChild>
86-
<div>
87-
<LanguageModelSelector
88-
languageModels={languageModels}
89-
onSelectedModelChange={setSelectedLanguageModel}
90-
selectedModel={selectedLanguageModel}
91-
/>
92-
</div>
93-
</TooltipTrigger>
94-
</Tooltip>
95-
</>
96-
)}
38+
<SearchScopeSelector
39+
className="bg-inherit w-fit h-6 min-h-6"
40+
repos={repos}
41+
searchContexts={searchContexts}
42+
selectedSearchScopes={selectedSearchScopes}
43+
onSelectedSearchScopesChange={onSelectedSearchScopesChange}
44+
isOpen={isContextSelectorOpen}
45+
onOpenChanged={onContextSelectorOpenChanged}
46+
/>
47+
<Separator orientation="vertical" className="h-3 ml-1 mr-2" />
48+
<LanguageModelSelector
49+
languageModels={languageModels}
50+
onSelectedModelChange={setSelectedLanguageModel}
51+
selectedModel={selectedLanguageModel}
52+
/>
9753
</>
9854
)
9955
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { BotIcon } from "lucide-react";
2+
import Link from "next/link";
3+
4+
export const LanguageModelInfoCard = () => {
5+
return (
6+
<div className="bg-popover border border-border rounded-lg shadow-lg p-4 w-80 max-w-[90vw]">
7+
<div className="flex items-center gap-2 mb-3 pb-2 border-b border-border/50">
8+
<BotIcon className="h-4 w-4 text-primary" />
9+
<h4 className="text-sm font-semibold text-popover-foreground">Language Model</h4>
10+
</div>
11+
<div className="text-sm text-popover-foreground leading-relaxed">
12+
Select the language model to use for the chat. <Link href="https://docs.sourcebot.dev/docs/configuration/language-model-providers" target="_blank" className="text-link">Configuration docs.</Link>
13+
</div>
14+
</div>
15+
);
16+
};

0 commit comments

Comments
 (0)