Skip to content

Commit

Permalink
Merge branch 'stackblitz-labs:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
grand151 authored Jan 30, 2025
2 parents dce5ceb + 137e268 commit e629737
Show file tree
Hide file tree
Showing 31 changed files with 716 additions and 179 deletions.
82 changes: 75 additions & 7 deletions app/components/chat/AssistantMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
import { memo } from 'react';
import { Markdown } from './Markdown';
import type { JSONValue } from 'ai';
import type { ProgressAnnotation } from '~/types/context';
import Popover from '~/components/ui/Popover';
import { workbenchStore } from '~/lib/stores/workbench';
import { WORK_DIR } from '~/utils/constants';

interface AssistantMessageProps {
content: string;
annotations?: JSONValue[];
}

function openArtifactInWorkbench(filePath: string) {
filePath = normalizedFilePath(filePath);

if (workbenchStore.currentView.get() !== 'code') {
workbenchStore.currentView.set('code');
}

workbenchStore.setSelectedFile(`${WORK_DIR}/${filePath}`);
}

function normalizedFilePath(path: string) {
let normalizedPath = path;

if (normalizedPath.startsWith(WORK_DIR)) {
normalizedPath = path.replace(WORK_DIR, '');
}

if (normalizedPath.startsWith('/')) {
normalizedPath = normalizedPath.slice(1);
}

return normalizedPath;
}

export const AssistantMessage = memo(({ content, annotations }: AssistantMessageProps) => {
const filteredAnnotations = (annotations?.filter(
(annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
) || []) as { type: string; value: any } & { [key: string]: any }[];

let progressAnnotation: ProgressAnnotation[] = filteredAnnotations.filter(
(annotation) => annotation.type === 'progress',
) as ProgressAnnotation[];
progressAnnotation = progressAnnotation.sort((a, b) => b.value - a.value);
let chatSummary: string | undefined = undefined;

if (filteredAnnotations.find((annotation) => annotation.type === 'chatSummary')) {
chatSummary = filteredAnnotations.find((annotation) => annotation.type === 'chatSummary')?.summary;
}

let codeContext: string[] | undefined = undefined;

if (filteredAnnotations.find((annotation) => annotation.type === 'codeContext')) {
codeContext = filteredAnnotations.find((annotation) => annotation.type === 'codeContext')?.files;
}

const usage: {
completionTokens: number;
Expand All @@ -29,8 +61,44 @@ export const AssistantMessage = memo(({ content, annotations }: AssistantMessage
<div className="overflow-hidden w-full">
<>
<div className=" flex gap-2 items-center text-sm text-bolt-elements-textSecondary mb-2">
{progressAnnotation.length > 0 && (
<Popover trigger={<div className="i-ph:info" />}>{progressAnnotation[0].message}</Popover>
{(codeContext || chatSummary) && (
<Popover side="right" align="start" trigger={<div className="i-ph:info" />}>
{chatSummary && (
<div className="max-w-chat">
<div className="summary max-h-96 flex flex-col">
<h2 className="border border-bolt-elements-borderColor rounded-md p4">Summary</h2>
<div style={{ zoom: 0.7 }} className="overflow-y-auto m4">
<Markdown>{chatSummary}</Markdown>
</div>
</div>
{codeContext && (
<div className="code-context flex flex-col p4 border border-bolt-elements-borderColor rounded-md">
<h2>Context</h2>
<div className="flex gap-4 mt-4 bolt" style={{ zoom: 0.6 }}>
{codeContext.map((x) => {
const normalized = normalizedFilePath(x);
return (
<>
<code
className="bg-bolt-elements-artifacts-inlineCode-background text-bolt-elements-artifacts-inlineCode-text px-1.5 py-1 rounded-md text-bolt-elements-item-contentAccent hover:underline cursor-pointer"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
openArtifactInWorkbench(normalized);
}}
>
{normalized}
</code>
</>
);
})}
</div>
</div>
)}
</div>
)}
<div className="context"></div>
</Popover>
)}
{usage && (
<div>
Expand Down
18 changes: 16 additions & 2 deletions app/components/chat/BaseChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @ts-nocheck
* Preventing TS checks with files presented in the video for a better presentation.
*/
import type { Message } from 'ai';
import type { JSONValue, Message } from 'ai';
import React, { type RefCallback, useEffect, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only';
import { Menu } from '~/components/sidebar/Menu.client';
Expand Down Expand Up @@ -32,6 +32,8 @@ import StarterTemplates from './StarterTemplates';
import type { ActionAlert } from '~/types/actions';
import ChatAlert from './ChatAlert';
import type { ModelInfo } from '~/lib/modules/llm/types';
import ProgressCompilation from './ProgressCompilation';
import type { ProgressAnnotation } from '~/types/context';

const TEXTAREA_MIN_HEIGHT = 76;

Expand Down Expand Up @@ -64,6 +66,7 @@ interface BaseChatProps {
setImageDataList?: (dataList: string[]) => void;
actionAlert?: ActionAlert;
clearAlert?: () => void;
data?: JSONValue[] | undefined;
}

export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
Expand Down Expand Up @@ -97,6 +100,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
messages,
actionAlert,
clearAlert,
data,
},
ref,
) => {
Expand All @@ -108,7 +112,15 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
const [recognition, setRecognition] = useState<SpeechRecognition | null>(null);
const [transcript, setTranscript] = useState('');
const [isModelLoading, setIsModelLoading] = useState<string | undefined>('all');

const [progressAnnotations, setProgressAnnotations] = useState<ProgressAnnotation[]>([]);
useEffect(() => {
if (data) {
const progressList = data.filter(
(x) => typeof x === 'object' && (x as any).type === 'progress',
) as ProgressAnnotation[];
setProgressAnnotations(progressList);
}
}, [data]);
useEffect(() => {
console.log(transcript);
}, [transcript]);
Expand Down Expand Up @@ -307,6 +319,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
className={classNames('pt-6 px-2 sm:px-6', {
'h-full flex flex-col': chatStarted,
})}
ref={scrollRef}
>
<ClientOnly>
{() => {
Expand Down Expand Up @@ -337,6 +350,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
/>
)}
</div>
{progressAnnotations && <ProgressCompilation data={progressAnnotations} />}
<div
className={classNames(
'bg-bolt-elements-background-depth-2 p-3 rounded-lg border border-bolt-elements-borderColor relative w-full max-w-chat mx-auto z-prompt',
Expand Down
72 changes: 43 additions & 29 deletions app/components/chat/Chat.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,36 +137,49 @@ export const ChatImpl = memo(

const [apiKeys, setApiKeys] = useState<Record<string, string>>({});

const { messages, isLoading, input, handleInputChange, setInput, stop, append, setMessages, reload, error } =
useChat({
api: '/api/chat',
body: {
apiKeys,
files,
promptId,
contextOptimization: contextOptimizationEnabled,
},
sendExtraMessageFields: true,
onError: (e) => {
logger.error('Request failed\n\n', e, error);
toast.error(
'There was an error processing your request: ' + (e.message ? e.message : 'No details were returned'),
);
},
onFinish: (message, response) => {
const usage = response.usage;

if (usage) {
console.log('Token usage:', usage);

// You can now use the usage data as needed
}
const {
messages,
isLoading,
input,
handleInputChange,
setInput,
stop,
append,
setMessages,
reload,
error,
data: chatData,
setData,
} = useChat({
api: '/api/chat',
body: {
apiKeys,
files,
promptId,
contextOptimization: contextOptimizationEnabled,
},
sendExtraMessageFields: true,
onError: (e) => {
logger.error('Request failed\n\n', e, error);
toast.error(
'There was an error processing your request: ' + (e.message ? e.message : 'No details were returned'),
);
},
onFinish: (message, response) => {
const usage = response.usage;
setData(undefined);

if (usage) {
console.log('Token usage:', usage);

// You can now use the usage data as needed
}

logger.debug('Finished streaming');
},
initialMessages,
initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
});
logger.debug('Finished streaming');
},
initialMessages,
initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
});
useEffect(() => {
const prompt = searchParams.get('prompt');

Expand Down Expand Up @@ -535,6 +548,7 @@ export const ChatImpl = memo(
setImageDataList={setImageDataList}
actionAlert={actionAlert}
clearAlert={() => workbenchStore.clearAlert()}
data={chatData}
/>
);
},
Expand Down
9 changes: 5 additions & 4 deletions app/components/chat/GitCloneButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import ignore from 'ignore';
import { useGit } from '~/lib/hooks/useGit';
import type { Message } from 'ai';
import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands';
import { detectProjectCommands, createCommandsMessage, escapeBoltTags } from '~/utils/projectCommands';
import { generateId } from '~/utils/fileUtils';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
import type { IChatMetadata } from '~/lib/persistence';

const IGNORE_PATTERNS = [
'node_modules/**',
Expand Down Expand Up @@ -35,7 +36,7 @@ const ig = ignore().add(IGNORE_PATTERNS);

interface GitCloneButtonProps {
className?: string;
importChat?: (description: string, messages: Message[]) => Promise<void>;
importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise<void>;
}

export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
Expand Down Expand Up @@ -83,7 +84,7 @@ ${fileContents
.map(
(file) =>
`<boltAction type="file" filePath="${file.path}">
${file.content}
${escapeBoltTags(file.content)}
</boltAction>`,
)
.join('\n')}
Expand All @@ -98,7 +99,7 @@ ${file.content}
messages.push(commandsMessage);
}

await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages, { gitUrl: repoUrl });
}
} catch (error) {
console.error('Error during import:', error);
Expand Down
2 changes: 0 additions & 2 deletions app/components/chat/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
const components = useMemo(() => {
return {
div: ({ className, children, node, ...props }) => {
console.log(className, node);

if (className?.includes('__boltArtifact__')) {
const messageId = node?.properties.dataMessageId as string;

Expand Down
Loading

0 comments on commit e629737

Please sign in to comment.