Skip to content

Commit

Permalink
Add Together AI model support
Browse files Browse the repository at this point in the history
  • Loading branch information
xKevIsDev committed Oct 31, 2024
1 parent cecbc55 commit a93e91c
Show file tree
Hide file tree
Showing 23 changed files with 25,634 additions and 89 deletions.
18 changes: 17 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ pnpm install
ANTHROPIC_API_KEY=XXX
```

```
TOGETHER_API_KEY=XXX
```

Optionally, you can set the debug level:

```
Expand All @@ -70,9 +74,21 @@ VITE_LOG_LEVEL=debug

**Important**: Never commit your `.env.local` file to version control. It's already included in .gitignore.

## Add Custom Models from Together AI

To add custom models from Together AI, you can add them to the `app/components/chat/ProviderSelector.tsx` file.

```
const togetherModels = [
'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo',
'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
'mistralai/Mixtral-8x7B-Instruct-v0.1',
'... add more models here ...'
];
## Available Scripts
- `pnpm run dev`: Starts the development server.
- `pnpm run dev`: Starts the development server (use Chrome Canary for best results when testing locally).
- `pnpm run build`: Builds the project.
- `pnpm run start`: Runs the built application locally using Wrangler Pages. This script uses `bindings.sh` to set up necessary bindings so you don't have to duplicate environment variables.
- `pnpm run preview`: Builds the project and then starts it locally, useful for testing the production build. Note, HTTP streaming currently doesn't work as expected with `wrangler pages dev`.
Expand Down
2 changes: 1 addition & 1 deletion app/components/chat/Artifact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
>
<div className="px-5 p-3.5 w-full text-left">
<div className="w-full text-bolt-elements-textPrimary font-medium leading-5 text-sm">{artifact?.title}</div>
<div className="w-full w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
<div className="w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
</div>
</button>
<div className="bg-bolt-elements-artifacts-borderColor w-[1px]" />
Expand Down
105 changes: 50 additions & 55 deletions app/components/chat/Chat.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { fileModificationsToHTML } from '~/utils/diff';
import { cubicEasingFn } from '~/utils/easings';
import { createScopedLogger, renderLogger } from '~/utils/logger';
import { BaseChat } from './BaseChat';
import { ProviderSelector } from './ProviderSelector';
import { providerStore } from '~/lib/stores/provider';

const toastAnimation = cssTransition({
enter: 'animated fadeInRight',
Expand Down Expand Up @@ -75,6 +77,8 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp

const [animationScope, animate] = useAnimate();

const provider = useStore(providerStore);

const { messages, isLoading, input, handleInputChange, setInput, stop, append } = useChat({
api: '/api/chat',
onError: (error) => {
Expand All @@ -85,6 +89,9 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
logger.debug('Finished streaming');
},
initialMessages,
body: {
provider: provider === 'anthropic' ? 'anthropic' : { type: 'together', model: provider.model },
},
});

const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
Expand Down Expand Up @@ -153,13 +160,6 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
return;
}

/**
* @note (delm) Usually saving files shouldn't take long but it may take longer if there
* many unsaved files. In that case we need to block user input and show an indicator
* of some kind so the user is aware that something is happening. But I consider the
* happy case to be no unsaved files and I would expect users to save their changes
* before they send another message.
*/
await workbenchStore.saveAllFiles();

const fileModifications = workbenchStore.getFileModifcations();
Expand All @@ -171,64 +171,59 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
if (fileModifications !== undefined) {
const diff = fileModificationsToHTML(fileModifications);

/**
* If we have file modifications we append a new user message manually since we have to prefix
* the user input with the file modifications and we don't want the new user input to appear
* in the prompt. Using `append` is almost the same as `handleSubmit` except that we have to
* manually reset the input and we'd have to manually pass in file attachments. However, those
* aren't relevant here.
*/
append({ role: 'user', content: `${diff}\n\n${_input}` });

/**
* After sending a new message we reset all modifications since the model
* should now be aware of all the changes.
*/
append({
role: 'user',
content: `${diff}\n\n${_input}`,
// We don't need to manually include the provider here as it's handled by the useChat hook
});

workbenchStore.resetAllFileModifications();
} else {
append({ role: 'user', content: _input });
append({
role: 'user',
content: _input,
// We don't need to manually include the provider here as it's handled by the useChat hook
});
}

setInput('');

resetEnhancer();

textareaRef.current?.blur();
};

const [messageRef, scrollRef] = useSnapScroll();

return (
<BaseChat
ref={animationScope}
textareaRef={textareaRef}
input={input}
showChat={showChat}
chatStarted={chatStarted}
isStreaming={isLoading}
enhancingPrompt={enhancingPrompt}
promptEnhanced={promptEnhanced}
sendMessage={sendMessage}
messageRef={messageRef}
scrollRef={scrollRef}
handleInputChange={handleInputChange}
handleStop={abort}
messages={messages.map((message, i) => {
if (message.role === 'user') {
return message;
}

return {
...message,
content: parsedMessages[i] || '',
};
})}
enhancePrompt={() => {
enhancePrompt(input, (input) => {
setInput(input);
scrollTextArea();
});
}}
/>
<BaseChat
ref={animationScope}
textareaRef={textareaRef}
input={input}
showChat={showChat}
chatStarted={chatStarted}
isStreaming={isLoading}
enhancingPrompt={enhancingPrompt}
promptEnhanced={promptEnhanced}
sendMessage={sendMessage}
messageRef={messageRef}
scrollRef={scrollRef}
handleInputChange={handleInputChange}
handleStop={abort}
messages={messages.map((message, i) => {
if (message.role === 'user') {
return message;
}

return {
...message,
content: parsedMessages[i] || '',
};
})}
enhancePrompt={() => {
enhancePrompt(input, (input) => {
setInput(input);
scrollTextArea();
});
}}
/>
);
});
});
60 changes: 60 additions & 0 deletions app/components/chat/ProviderSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { useStore } from '@nanostores/react';
import { providerStore, setProvider, type Provider } from '~/lib/stores/provider';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
} from '~/components/ui/dropdown-menu';
import { Button } from '~/components/ui/button';
import '~/styles/index.scss';
import { ChevronDownIcon } from '@radix-ui/react-icons';

export function ProviderSelector() {
const currentProvider = useStore(providerStore);

const handleProviderChange = (value: string) => {
if (value === 'anthropic') {
setProvider('anthropic');
} else {
setProvider({ type: 'together', model: value });
}
};

const togetherModels = [
'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo',
'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
'mistralai/Mixtral-8x7B-Instruct-v0.1',
// Add more Together AI models here
];

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-[250px] justify-between bg-transparent border-none ring-transparent outline-transparent text-white hover:text-white/80 truncate">
{currentProvider === 'anthropic'
? 'Anthropic (Claude)'
: `Together AI (${currentProvider.model})`}
<ChevronDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[250px] bg-black text-white border-none">
<DropdownMenuRadioGroup
value={currentProvider === 'anthropic' ? 'anthropic' : currentProvider.model}
onValueChange={handleProviderChange}
>
<DropdownMenuRadioItem value="anthropic" className="hover:bg-white/10">
Anthropic (Claude)
</DropdownMenuRadioItem>
{togetherModels.map(model => (
<DropdownMenuRadioItem key={model} value={model} className="hover:bg-white/10">
Together AI ({model})
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}
24 changes: 14 additions & 10 deletions app/components/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { chatStore } from '~/lib/stores/chat';
import { classNames } from '~/utils/classNames';
import { HeaderActionButtons } from './HeaderActionButtons.client';
import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
import { ProviderSelector } from '../chat/ProviderSelector';

export function Header() {
const chat = useStore(chatStore);

return (
<header
className={classNames(
'flex items-center bg-bolt-elements-background-depth-1 p-5 border-b h-[var(--header-height)]',
'flex items-center justify-between bg-bolt-elements-background-depth-1 p-5 border-b h-[var(--header-height)]',
{
'border-transparent': !chat.started,
'border-bolt-elements-borderColor': chat.started,
Expand All @@ -27,15 +28,18 @@ export function Header() {
<span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
<ClientOnly>{() => <ChatDescription />}</ClientOnly>
</span>
{chat.started && (
<ClientOnly>
{() => (
<div className="mr-1">
<HeaderActionButtons />
</div>
)}
</ClientOnly>
)}
<div className="flex items-center gap-4">
<ClientOnly>{() => <ProviderSelector />}</ClientOnly>
{chat.started && (
<ClientOnly>
{() => (
<div className="mr-1">
<HeaderActionButtons />
</div>
)}
</ClientOnly>
)}
</div>
</header>
);
}
57 changes: 57 additions & 0 deletions app/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

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

const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"

export { Button, buttonVariants }
Loading

0 comments on commit a93e91c

Please sign in to comment.