Skip to content

Commit

Permalink
Merge pull request #54 from dwhiffing/feature/model-switching
Browse files Browse the repository at this point in the history
Added Chat window settings
  • Loading branch information
mafzal91 authored Sep 11, 2024
2 parents f096e54 + 3daaeb6 commit 7d2e032
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 73 deletions.
5 changes: 4 additions & 1 deletion app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { reasoningPrompt } from "@/utils/reasoningPrompt";
import { getStructuredPrompt } from "@/utils/prompts";
import { timestampLambda } from "@/utils/timestampLambda";
import { RunnableSequence } from "@langchain/core/runnables";
import { MODELS } from "@/constants";

export const runtime = "nodejs";

export async function POST(req: NextRequest) {
try {
const body = await req.json();
const messages = body.messages ?? [];
const modelName = body.modelName ?? MODELS["openai"][0];
const contracts = (await contractCollection.get()).filter(
(c) => !(body.disabledContractKeys ?? []).includes(c.key),
);
Expand All @@ -28,6 +30,7 @@ export async function POST(req: NextRequest) {
// Reasoning prompt takes the contracts and chat history to asks the llm to reduce the # of abi functions
// It returns an object of the contract and abis most appropriate to the chat history
const reasoningPromptResponse = await reasoningPrompt({
modelName: modelName,
contracts,
input: currentMessageContent,
chatHistory: previousMessages,
Expand Down Expand Up @@ -55,7 +58,7 @@ export async function POST(req: NextRequest) {
const tools = getToolsFromContracts(filteredContracts);

const model = new ChatOpenAI({
model: "gpt-4o-mini",
model: modelName,
temperature: 0,
streaming: true,
}).bindTools(tools);
Expand Down
5 changes: 1 addition & 4 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ export default function Page() {
)}

{/* Remove div with id=temp if enabling side nav */}
<div
id="temp"
className="mx-auto w-full max-w-6xl py-3 flex flex-col gap-2"
>
<div id="temp" className="w-full py-3 flex flex-col gap-2">
<div className="flex flex-1 flex-col">
<ChatWindow titleText="Magic Chat Prototype" />

Expand Down
90 changes: 90 additions & 0 deletions components/ChatSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use client";

import { useState } from "react";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "./ui/checkbox";
import { MODELS } from "@/constants";
import { findModelKey } from "@/utils/utils";

type IChatSettingProps = {
clearOnChange: boolean;
onClearOnChange: (value: boolean) => void;
modelName: string;
onModelNameChange: (value: string) => void;
};

export function ChatSettings(props: IChatSettingProps) {
const { modelName, onModelNameChange, clearOnChange, onClearOnChange } =
props;
const [inferenceProvider, setInferenceProvider] = useState(() => {
return findModelKey(modelName);
});

const handleInferenceProviderChange = (value: keyof typeof MODELS) => {
setInferenceProvider(value);
};

return (
<div className="max-w-80 min-w-80 border-l p-4 overflow-y-auto">
<h3 className="font-semibold mb-4">Settings</h3>
<div className="space-y-4">
<div>
<Label htmlFor="inferenceProvider">Inference</Label>
<Select
value={inferenceProvider}
onValueChange={handleInferenceProviderChange}
>
<SelectTrigger id="modelName">
<SelectValue placeholder="Select a provider" />
</SelectTrigger>
<SelectContent>
{Object.keys(MODELS).map((i) => (
<SelectItem key={i} value={i}>
{i}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{inferenceProvider && (
<div>
<Label htmlFor="modelName">Model</Label>
<Select value={modelName} onValueChange={onModelNameChange}>
<SelectTrigger id="modelName">
<SelectValue placeholder="Select a model" />
</SelectTrigger>
<SelectContent>
{MODELS[inferenceProvider].map((modelName) => (
<SelectItem key={modelName} value={modelName}>
{modelName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}

<div className="flex items-center space-x-2">
<Checkbox
id="terms"
checked={clearOnChange}
onCheckedChange={onClearOnChange}
/>
<label
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Clear Chat on Model change
</label>
</div>
</div>
</div>
);
}
152 changes: 86 additions & 66 deletions components/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

import { toast } from "sonner";
import { useChat } from "ai/react";
import { useEffect, useRef, type FormEvent } from "react";

import { useEffect, useRef, useState, type FormEvent } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Expand All @@ -15,15 +14,20 @@ import {
} from "@/components/ui/card";
import { ChatMessageBubble } from "@/components/ChatMessageBubble";
import { LoadingIcon } from "@/components/LoadingIcon";
import { Label } from "./ui/label";
import { Label } from "@/components/ui/label";
import { CornerDownLeft, Trash2 } from "lucide-react";
import { ConfirmAlert } from "./ConfirmAlert";
import { useContracts } from "@/utils/useContracts";
import { ChatSettings } from "./ChatSettings";
import { MODELS } from "@/constants";

export function ChatWindow(props: { titleText?: string }) {
const { titleText } = props;
const chatContainerRef = useRef<HTMLDivElement>(null);
const { disabledKeys } = useContracts();
const [modelName, setModelName] = useState(MODELS["openai"][0]);
const [clearOnChange, setClearOnChange] = useState(false);

const {
messages,
input,
Expand All @@ -33,7 +37,7 @@ export function ChatWindow(props: { titleText?: string }) {
isLoading,
} = useChat({
api: "api/chat",
body: { disabledContractKeys: disabledKeys },
body: { disabledContractKeys: disabledKeys, modelName },
streamProtocol: "text",
onError: (e) => {
toast(e.message);
Expand Down Expand Up @@ -66,7 +70,6 @@ export function ChatWindow(props: { titleText?: string }) {
return () => observer.disconnect();
}
}, []);

async function sendMessage(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
if (!messages.length) {
Expand All @@ -82,69 +85,86 @@ export function ChatWindow(props: { titleText?: string }) {
setMessages([]);
};

const onModelNameChange = (value: string) => {
if (clearOnChange) {
onClearMessages();
}
setModelName(value);
};

return (
<Card className="flex flex-col h-[calc(100vh-6rem)] border-none shadow-none">
<CardHeader>
<CardTitle>{titleText}</CardTitle>
</CardHeader>
<CardContent className="flex-grow overflow-hidden p-0">
<div ref={chatContainerRef} className="h-full p-4 overflow-y-auto">
<div className="grid gap-4">
{messages.length > 0
? messages.map((m) => (
<ChatMessageBubble key={m.id} message={m} />
))
: null}
<div className="flex">
<Card className="flex grow flex-col h-[calc(100vh-6rem)] border-none shadow-none">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>{titleText}</CardTitle>
</CardHeader>
<CardContent className="flex-grow overflow-hidden p-0">
<div className="flex h-full">
<div ref={chatContainerRef} className="flex-1 p-4 overflow-y-auto">
<div className="grid gap-4">
{messages.length > 0
? messages.map((m) => (
<ChatMessageBubble key={m.id} message={m} />
))
: null}
</div>
</div>
</div>
</div>
</CardContent>
<CardFooter>
<form
onSubmit={sendMessage}
className="w-full relative overflow-hidden rounded-lg border bg-background"
>
<Label htmlFor="message" className="sr-only">
Message
</Label>
<Input
id="message"
className="min-h-12 border-0 p-3 pb-2 shadow-none focus-visible:ring-0"
value={input}
placeholder="Type your message here..."
onChange={handleInputChange}
/>
<div className="flex items-center p-2 pt-0">
<ConfirmAlert
onConfirm={onClearMessages}
description="This will clear out your chat history"
button={
<Button variant="ghost" size="icon" title="Clear messages">
<Trash2 strokeWidth={1.5} size={20} />
<span className="sr-only">Clear messages</span>
</Button>
}
</CardContent>
<CardFooter>
<form
onSubmit={sendMessage}
className="w-full relative overflow-hidden rounded-lg border bg-background"
>
<Label htmlFor="message" className="sr-only">
Message
</Label>
<Input
id="message"
className="min-h-12 border-0 p-3 pb-2 shadow-none focus-visible:ring-0"
value={input}
placeholder="Type your message here..."
onChange={handleInputChange}
/>
<Button
type="submit"
size="sm"
className="ml-auto gap-1.5"
disabled={isLoading}
>
{isLoading ? (
<div role="status" className="flex justify-center">
<LoadingIcon />
<span className="sr-only">Loading...</span>
</div>
) : (
<>
Send
<CornerDownLeft strokeWidth={1.5} size={20} />
</>
)}
</Button>
</div>
</form>
</CardFooter>
</Card>
<div className="flex items-center p-2 pt-0">
<ConfirmAlert
onConfirm={onClearMessages}
description="This will clear out your chat history"
button={
<Button variant="ghost" size="icon" title="Clear messages">
<Trash2 strokeWidth={1.5} size={20} />
<span className="sr-only">Clear messages</span>
</Button>
}
/>
<Button
type="submit"
size="sm"
className="ml-auto gap-1.5"
disabled={isLoading}
>
{isLoading ? (
<div role="status" className="flex justify-center">
<LoadingIcon />
<span className="sr-only">Loading...</span>
</div>
) : (
<>
Send
<CornerDownLeft strokeWidth={1.5} size={20} />
</>
)}
</Button>
</div>
</form>
</CardFooter>
</Card>
<ChatSettings
clearOnChange={clearOnChange}
onClearOnChange={setClearOnChange}
modelName={modelName}
onModelNameChange={onModelNameChange}
/>
</div>
);
}
30 changes: 30 additions & 0 deletions components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client"

import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"

import { cn } from "@/lib/utils"

const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName

export { Checkbox }
11 changes: 11 additions & 0 deletions constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { IContract } from "@/types";

export const MODELS = {
openai: ["gpt-4o-mini", "gpt-4o", "gpt-4o-2024-08-06", "gpt-4o-latest"],
together: [
"mistralai/Mistral-7B-Instruct-v0.3",
"meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
"meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo",
],
ollama: ["mistral", "llama3.1"],
};

export const CHAINS = {
11155111: {
name: "ETH Sepolia",
Expand Down
Loading

0 comments on commit 7d2e032

Please sign in to comment.