Skip to content

Commit 505d36e

Browse files
committed
feat: Add AI model selection to the chat composer with enhanced action sheet components and model definitions.
1 parent a41b0c0 commit 505d36e

31 files changed

+262
-38
lines changed

apps/mobile/components/ai-chat/attachment.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export const ComposerAddAttachmentSheet: FC = () => {
157157
};
158158

159159
return (
160-
<View className="flex flex-row gap-4 p-6 pt-2">
160+
<View className="flex flex-row gap-4">
161161
<ComposerAddAttachmentTakePhoto onAddAttachment={handleAddAttachment} />
162162
<ComposerAddAttachmentImage onAddAttachment={handleAddAttachment} />
163163
<ComposerAddAttachmentFile onAddAttachment={handleAddAttachment} />

apps/mobile/components/ai-chat/thread.tsx

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, type ReactNode } from "react";
1+
import { FC, useCallback, type ReactNode } from "react";
22
import { View } from "react-native";
33

44
import { Icon, IconName } from "../ui/icon";
@@ -33,6 +33,19 @@ import {
3333
} from "./attachment";
3434
import { WeatherToolRegistration } from "../tools/weather-tool-ui";
3535
import { Reasoning, ReasoningGroup } from "./reasoning";
36+
import { Divider } from "../ui/divider";
37+
import {
38+
ActionSheetSelect,
39+
ActionSheetSelectContent,
40+
ActionSheetSelectItem,
41+
ActionSheetSelectTrigger,
42+
ActionSheetSelectValue,
43+
} from "../ui/action-sheet-select";
44+
import { AI_MODELS, DEFAULT_AI_MODEL } from "~/lib/ai-constants";
45+
import {
46+
useAiContext,
47+
useAiContextStore,
48+
} from "@creatorem/ai-chat/ai-provider";
3649

3750
export const Thread: React.FC = () => {
3851
return (
@@ -208,6 +221,91 @@ const Composer: React.FC = () => {
208221
);
209222
};
210223

224+
const modelLabels: Record<(typeof AI_MODELS)[number], React.ReactNode> = {
225+
[DEFAULT_AI_MODEL]: (
226+
<View className="flex-1 p-2">
227+
<View className="mb-1 flex flex-row gap-2 text-lg">
228+
<Text>{DEFAULT_AI_MODEL}</Text>
229+
<Icon name="Gauge" size={20} />
230+
</View>
231+
<Text className="text-muted-foreground text-sm">
232+
Optimized for a wide range of natural language tasks.
233+
</Text>
234+
</View>
235+
),
236+
"meta-llama/llama-4-scout-17b-16e-instruct": (
237+
<View className="flex-1 p-2">
238+
<View className="mb-1 flex flex-row gap-2 text-lg">
239+
<Text>meta-llama/llama-4-scout-17b-16e-instruct</Text>
240+
<Icon name="Image" size={20} />
241+
</View>
242+
<Text className="text-muted-foreground text-sm">
243+
Designed for high-capability agentic use.
244+
</Text>
245+
</View>
246+
),
247+
"openai/gpt-oss-120b": (
248+
<View className="flex-1 p-2">
249+
<View className="mb-1 flex flex-row gap-2 text-lg">
250+
<Text>openai/gpt-oss-120b</Text>
251+
<Icon name="Brain" size={20} />
252+
</View>
253+
<Text className="text-muted-foreground text-sm">
254+
Competitive math/coding performance
255+
</Text>
256+
</View>
257+
),
258+
"qwen/qwen3-32b": (
259+
<View className="flex-1 p-2">
260+
<View className="mb-1 flex flex-row gap-2 text-lg">
261+
<Text>qwen/qwen3-32b</Text>
262+
<Icon name="Brain" size={20} />
263+
</View>
264+
<Text className="text-muted-foreground text-sm">
265+
Groundbreaking advancements in reasoning
266+
</Text>
267+
</View>
268+
),
269+
};
270+
271+
const ModelSelector: React.FC = () => {
272+
const aiContextStore = useAiContextStore();
273+
const selectedModel = useAiContext((s) => s.selectedModel);
274+
275+
const handleChange = useCallback(
276+
(value: string) => {
277+
aiContextStore.setState({ selectedModel: value });
278+
},
279+
[aiContextStore],
280+
);
281+
282+
return (
283+
<ActionSheetSelect
284+
value={selectedModel}
285+
onValueChange={handleChange}
286+
labels={modelLabels}
287+
>
288+
<View className="flex gap-1">
289+
<Text className="font-bold">Model</Text>
290+
<ActionSheetSelectTrigger className="border-none">
291+
<ActionSheetSelectValue
292+
placeholder={selectedModel ?? DEFAULT_AI_MODEL}
293+
/>
294+
</ActionSheetSelectTrigger>
295+
</View>
296+
<ActionSheetSelectContent>
297+
{Object.keys(modelLabels).map((model) => (
298+
<ActionSheetSelectItem
299+
key={model}
300+
value={model}
301+
checkClassName="absolute top-2 right-2"
302+
/>
303+
))}
304+
</ActionSheetSelectContent>
305+
</ActionSheetSelect>
306+
);
307+
};
308+
211309
const ComposerAction: FC = () => {
212310
const textMutedForeground = useCSSVariable("--color-muted-foreground");
213311
const borderColor = useCSSVariable("--color-border");
@@ -227,7 +325,11 @@ const ComposerAction: FC = () => {
227325
backgroundColor: backgroundColor,
228326
}}
229327
>
230-
<ComposerAddAttachmentSheet />
328+
<View className="flex gap-4 p-6 pt-2">
329+
<ComposerAddAttachmentSheet />
330+
<Divider />
331+
<ModelSelector />
332+
</View>
231333
{/* <View className="h-40 px-4">
232334
<ComposerAddAttachmentFile />
233335
<ComposerAddAttachmentImage />

apps/mobile/components/ui/action-sheet-select.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export const ActionSheetSelectTrigger: React.FC<
7070
return (
7171
<ActionSheetTrigger
7272
className={cn(
73-
"h-12 flex-row items-center justify-between rounded-xl border px-3",
73+
"min-h-12 flex-row items-center justify-between rounded-xl border px-3",
7474
open ? "border-black dark:border-white" : "border-input",
7575
className,
7676
)}
@@ -92,8 +92,10 @@ export const ActionSheetSelectValue: React.FC<ActionSheetSelectValueProps> = ({
9292
}) => {
9393
const { value, labels } = useActionSheetSelect();
9494
const label = labels[value] ?? null;
95-
return (
95+
return typeof label === "string" || !label ? (
9696
<Text className={className}>{label ?? placeholder ?? "Select..."}</Text>
97+
) : (
98+
label
9799
);
98100
};
99101

@@ -110,11 +112,13 @@ export const ActionSheetSelectContent: React.FC<
110112
interface ActionSheetSelectItemProps {
111113
className?: string;
112114
value: string;
115+
checkClassName?: string;
113116
}
114117

115118
export const ActionSheetSelectItem: React.FC<ActionSheetSelectItemProps> = ({
116119
className,
117120
value,
121+
checkClassName,
118122
...props
119123
}) => {
120124
const { value: controlledValue, setValue, labels } = useActionSheetSelect();
@@ -130,16 +134,24 @@ export const ActionSheetSelectItem: React.FC<ActionSheetSelectItemProps> = ({
130134
<Pressable
131135
{...props}
132136
className={cn(
133-
"flex-row items-center gap-2 rounded-xl px-3 py-1.5",
137+
"flex-row items-center gap-2 rounded-xl px-3 py-1.5 active:bg-accent",
134138
value === controlledValue ? "bg-accent" : "",
135139
className,
136140
)}
137141
onPress={handleChange}
138142
>
139143
<View className={"flex-row items-center gap-2"}>
140-
<Text className="flex-1">{label}</Text>
144+
{typeof label === "string" ? (
145+
<Text className="flex-1">{label}</Text>
146+
) : (
147+
label
148+
)}
141149
{value === controlledValue && (
142-
<Icon name="Check" className="h-4 w-4" size={20} />
150+
<Icon
151+
name="Check"
152+
className={cn("h-4 w-4", checkClassName)}
153+
size={20}
154+
/>
143155
)}
144156
</View>
145157
</Pressable>

apps/mobile/components/ui/icon.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
PencilIcon,
1717
BrainIcon,
1818
PaperclipIcon,
19+
GaugeIcon,
1920
ChevronRightIcon,
2021
ExpandIcon,
2122
MoreHorizontalIcon,
@@ -125,6 +126,7 @@ const lucideIcons = {
125126
Star: StarIcon,
126127
Sun: SunIcon,
127128
Moon: MoonIcon,
129+
Gauge: GaugeIcon,
128130
RefreshCw: RefreshCwIcon,
129131
LogOut: LogOutIcon,
130132
Paperclip: PaperclipIcon,

apps/mobile/lib/ai-constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export const DEFAULT_AI_MODEL = 'llama-3.3-70b-versatile'
1+
export const DEFAULT_AI_MODEL = 'llama-3.3-70b-versatile'
2+
3+
export const AI_MODELS = [DEFAULT_AI_MODEL, "meta-llama/llama-4-scout-17b-16e-instruct", "openai/gpt-oss-120b", "qwen/qwen3-32b"] as const

apps/nextjs-example/app/api/chat/route.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ export async function POST(req: Request) {
1616
const toolsFilter = getDisabledToolsFiter(body);
1717

1818
const result = streamText({
19-
// model: groq('llama-3.3-70b-versatile'),
20-
// model: groq('meta-llama/llama-4-scout-17b-16e-instruct'),
21-
// model: groq('meta-llama/llama-4-scout-17b-16e-instruct'),
2219
model,
2320
messages: await convertToModelMessages(messages),
2421
tools: toolsFilter({

apps/nextjs-example/app/test2/attachment.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
TooltipContent,
2020
TooltipTrigger,
2121
} from "@/components/ui/tooltip";
22-
import { cn } from "@/lib/cn";
22+
import { cn } from "@/lib/utils";
2323

2424
const useFileSrc = (file: File | undefined) => {
2525
const [src, setSrc] = useState<string | undefined>(undefined);

apps/nextjs-example/app/test2/markdown-text.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { CheckIcon, CopyIcon } from "lucide-react";
1313
import { type FC, memo, useState } from "react";
1414
import remarkGfm from "remark-gfm";
1515
import { TooltipIconButton } from "@/components/ai-chat/tooltip-icon-button";
16-
import { cn } from "@/lib/cn";
16+
import { cn } from "@/lib/utils";
1717

1818
const MarkdownTextImpl = () => {
1919
return (

0 commit comments

Comments
 (0)