Skip to content

Commit

Permalink
feat(code-template): code template with AI tool Calling Feature added…
Browse files Browse the repository at this point in the history
… with optin feature
  • Loading branch information
thecodacus committed Nov 14, 2024
1 parent b16a457 commit 2776200
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 51 deletions.
19 changes: 14 additions & 5 deletions app/components/chat/BaseChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { APIKeyManager } from './APIKeyManager';
import Cookies from 'js-cookie';

import styles from './BaseChat.module.scss';
import { ToolManager } from './ToolManager';

const EXAMPLE_PROMPTS = [
{ text: 'Build a todo app in React using Tailwind' },
Expand Down Expand Up @@ -92,6 +93,8 @@ interface BaseChatProps {
handleInputChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
enhancePrompt?: () => void;
addToolResult?: ({ toolCallId, result }: { toolCallId: string; result: any }) => void;
toolConfig: IToolsConfig;
onToolConfigChange?: (val: IToolsConfig) => void;
}

export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
Expand All @@ -116,6 +119,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
enhancePrompt,
handleStop,
addToolResult,
toolConfig,
onToolConfigChange,
},
ref,
) => {
Expand Down Expand Up @@ -208,11 +213,15 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
setProvider={setProvider}
providerList={providerList}
/>
<APIKeyManager
provider={provider}
apiKey={apiKeys[provider] || ''}
setApiKey={(key) => updateApiKey(provider, key)}
/>
<div className="flex justify-between">
<ToolManager toolConfig={toolConfig} onConfigChange={onToolConfigChange} />

<APIKeyManager
provider={provider}
apiKey={apiKeys[provider] || ''}
setApiKey={(key) => updateApiKey(provider, key)}
/>
</div>
<div
className={classNames(
'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
Expand Down
9 changes: 8 additions & 1 deletion app/components/chat/Chat.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { cubicEasingFn } from '~/utils/easings';
import { createScopedLogger, renderLogger } from '~/utils/logger';
import { BaseChat } from './BaseChat';
import Cookies from 'js-cookie';
import { toolStore } from '~/lib/stores/tool';

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

const { showChat } = useStore(chatStore);

const toolConfig = useStore(toolStore.config);

const [animationScope, animate] = useAnimate();

const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
Expand All @@ -91,7 +94,9 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
api: '/api/chat',
body: {
apiKeys,
toolEnabled: toolConfig.enabled,
},
maxSteps: 3,
onError: (error) => {
logger.error('Request failed\n\n', error);
toast.error('There was an error processing your request');
Expand All @@ -104,7 +109,7 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
if (toolCall.toolName == 'askForConfirmation') return;
logger.debug('Calling Tool:', toolCall);
try {
let result = await workbenchStore.handleToolCall({
let result = await toolStore.handleToolCall({
toolName: toolCall.toolName,
args: toolCall.args,
toolCallId: toolCall.toolCallId,
Expand Down Expand Up @@ -276,6 +281,8 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
handleInputChange={handleInputChange}
handleStop={abort}
addToolResult={addToolResult}
toolConfig={toolConfig}
onToolConfigChange={(config) => toolStore.setConfig(config)}
messages={messages.map((message, i) => {
if (message.role === 'user') {
return message;
Expand Down
16 changes: 12 additions & 4 deletions app/components/chat/Messages.client.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JSONValue, Message, ToolInvocation } from 'ai';
import React from 'react';
import React, { Fragment } from 'react';
import { classNames } from '~/utils/classNames';
import { AssistantMessage } from './AssistantMessage';
import { UserMessage } from './UserMessage';
Expand All @@ -24,15 +24,23 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
const isUserMessage = role === 'user';
const isFirst = index === 0;
const isLast = index === messages.length - 1;

// checking for annotated message marked as "hidden"
if (message.annotations) {
let isHidden = message.annotations.find((annotation: JSONValue) => {
if (typeof annotation !== 'object' || typeof annotation?.length === 'number') return false;
let object = annotation as any;
return object.visibility === 'hidden';
});
console.log('isHidden', isHidden, message);

if (isHidden) return <></>;
if (isHidden) return <Fragment key={index}></Fragment>;
}
//hide confirmation message that has been answered
if (
message.toolInvocations?.length == 1 &&
message.toolInvocations[0].toolName === 'askForConfirmation' &&
(message.toolInvocations[0] as any).result
) {
return <Fragment key={index}></Fragment>;
}
return (
<div
Expand Down
32 changes: 32 additions & 0 deletions app/components/chat/ToolManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IconButton } from '../ui/IconButton';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/Popover';
import { ToggleSwitch } from '../ui/ToggleSwitch';
import type { IToolsConfig } from '~/utils/types';

interface ToolManagerProps {
toolConfig: IToolsConfig;
onConfigChange?: (val: IToolsConfig) => void;
}

export function ToolManager({ toolConfig, onConfigChange }: ToolManagerProps) {
return (
<>
{toolConfig && (
<div className="grid gap-4 text-sm">
<div className="flex items-center gap-2">
<label className="text-sm text-bolt-elements-textSecondary">Tool Calling</label>
<ToggleSwitch
checked={toolConfig.enabled}
onCheckedChange={(e) => {
onConfigChange?.({
enabled: e,
config: toolConfig.config,
});
}}
/>
</div>
</div>
)}
</>
);
}
14 changes: 10 additions & 4 deletions app/components/chat/ToolMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const AskForConfirmation = (props: {
addToolResult?: ({ toolCallId, result }: { toolCallId: string; result: any }) => void;
}) => {
const toolCallId = props.toolInvocation.toolCallId;
const addResult = (result: string) => (props.addToolResult ? props.addToolResult({ toolCallId, result }) : null);
const addResult = (result: string) =>
props.addToolResult ? props.addToolResult({ toolCallId, result: result }) : null;

return (
<div className="overflow-hidden pt-[4px] flex flex-col gap-2">
Expand Down Expand Up @@ -53,8 +54,11 @@ export function ToolMessage({ content, data: result, addToolResult }: ToolMessag
result: (content as any).result,
});
}, [content]);
if (data?.name == 'askForConfirmation' && (data.state == 'result' || data.state == 'call')) {
return <AskForConfirmation toolInvocation={content} addToolResult={addToolResult}></AskForConfirmation>;
if (data?.name == 'askForConfirmation') {
if (!data.result) {
return <AskForConfirmation toolInvocation={content} addToolResult={addToolResult}></AskForConfirmation>;
}
return <></>;
}
return (
<div className="overflow-hidden pt-[4px]">
Expand Down Expand Up @@ -86,7 +90,9 @@ export function ToolMessage({ content, data: result, addToolResult }: ToolMessag
)}
{/* //results in dimmed tone */}
{data.state == 'result' && (
<div className="text-bolt-elements-textSecondary">Output: {(content as any).result}</div>
<div className="text-bolt-elements-textSecondary">
Output: {`${(content as any).result || ''}`.split('---')[0]}
</div>
)}
</div>
</>
Expand Down
47 changes: 47 additions & 0 deletions app/components/ui/Popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';

const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;

const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={[
'z-50',
'w-auto',
'rounded-md',
'border border-bolt-elements-borderColor',
'bg-bolt-elements-background-depth-2', // Using depth-2 for better contrast
'p-4',
'text-bolt-elements-textPrimary',
'shadow-md',
'outline-none',
// Focus and ring states could use your theme colors
// 'focus-visible:(outline-none ring-2 ring-bolt-elements-item-contentAccent ring-offset-2 ring-offset-bolt-elements-background-depth-1)',
// Transitions
'transition-all duration-800',
'transform-gpu',
// State animations
'data-[state=open]:(opacity-100 scale-100)',
'data-[state=closed]:(opacity-0 scale-95)',
// Position animations
'data-[side=bottom]:translate-y-2',
'data-[side=left]:translate-x-2',
'data-[side=right]:-translate-x-2',
'data-[side=top]:-translate-y-2',
className,
]
.filter(Boolean)
.join(' ')}
{...props}
/>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;

export { Popover, PopoverTrigger, PopoverContent };
55 changes: 55 additions & 0 deletions app/components/ui/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import * as SwitchPrimitives from '@radix-ui/react-switch';

const ToggleSwitch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={[
'peer',
'inline-flex',
'h-4',
'w-9',
'shrink-0',
'cursor-pointer',
'items-center',
'rounded-full',
'border-2',
'border-transparent',
'transition-colors duration-200 bolt-ease-cubic-bezier',
// Focus styles
'focus-visible:(outline-none ring-1)',
// Disabled styles
'disabled:(cursor-not-allowed opacity-50)',
// State styles
'data-[state=checked]:bg-bolt-elements-item-contentAccent',
'data-[state=unchecked]:bg-bolt-elements-button-secondary-background',
'hover:data-[state=unchecked]:bg-bolt-elements-button-secondary-backgroundHover',
className,
]
.filter(Boolean)
.join(' ')}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={[
'pointer-events-none',
'block',
'h-3',
'w-3',
'rounded-full',
'bg-bolt-elements-textPrimary',
'shadow-lg',
'ring-0',
'transition-transform duration-200 bolt-ease-cubic-bezier',
'data-[state=checked]:translate-x-5',
'data-[state=unchecked]:translate-x-0',
].join(' ')}
/>
</SwitchPrimitives.Root>
));
ToggleSwitch.displayName = SwitchPrimitives.Root.displayName;

export { ToggleSwitch };
3 changes: 1 addition & 2 deletions app/lib/.server/llm/stream-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ export function streamText(
...options,
// toolChoice: messages.length == 1 ? { type: 'tool', toolName: 'selectCodeTemplate' } : 'none',
toolChoice: 'auto',
tools: tools,
experimental_toolCallStreaming: true,
maxSteps: 2
maxSteps: 3
});
}
Loading

0 comments on commit 2776200

Please sign in to comment.