Skip to content

Commit

Permalink
Merge pull request #1 from replayio/recording-button
Browse files Browse the repository at this point in the history
Add button to save recording, assorted other UX changes
  • Loading branch information
bhackett1024 authored Jan 7, 2025
1 parent 1f938fc commit c3e1764
Show file tree
Hide file tree
Showing 22 changed files with 1,598 additions and 94 deletions.
12 changes: 6 additions & 6 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ if ! pnpm typecheck; then
fi

# Run lint
echo "Running lint..."
if ! pnpm lint; then
echo "❌ Linting failed! Run 'pnpm lint:fix' to fix the easy issues."
echo "Once you're done, don't forget to add your beautification to the commit! 🤩"
exit 1
fi
#echo "Running lint..."
#if ! pnpm lint; then
# echo "❌ Linting failed! Run 'pnpm lint:fix' to fix the easy issues."
# echo "Once you're done, don't forget to add your beautification to the commit! 🤩"
# exit 1
#fi

echo "👍 All checks passed! Committing changes..."
8 changes: 7 additions & 1 deletion app/components/chat/AssistantMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface AssistantMessageProps {
annotations?: JSONValue[];
}

export const AssistantMessage = memo(({ content, annotations }: AssistantMessageProps) => {
export function getAnnotationsTokensUsage(annotations: JSONValue[] | undefined) {
const filteredAnnotations = (annotations?.filter(
(annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
) || []) as { type: string; value: any }[];
Expand All @@ -18,6 +18,12 @@ export const AssistantMessage = memo(({ content, annotations }: AssistantMessage
totalTokens: number;
} = filteredAnnotations.find((annotation) => annotation.type === 'usage')?.value;

return usage;
}

export const AssistantMessage = memo(({ content, annotations }: AssistantMessageProps) => {
const usage = getAnnotationsTokensUsage(annotations);

return (
<div className="overflow-hidden w-full">
{usage && (
Expand Down
10 changes: 10 additions & 0 deletions app/components/chat/Chat.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { useSettings } from '~/lib/hooks/useSettings';
import type { ProviderInfo } from '~/types/model';
import { useSearchParams } from '@remix-run/react';
import { createSampler } from '~/utils/sampler';
import { saveProjectPrompt } from './Messages.client';
import { uint8ArrayToBase64 } from '../workbench/ReplayProtocolClient';

const toastAnimation = cssTransition({
enter: 'animated fadeInRight',
Expand Down Expand Up @@ -314,6 +316,14 @@ export const ChatImpl = memo(
resetEnhancer();

textareaRef.current?.blur();

// The project contents are associated with the last message present when
// the user message is added.
const lastMessage = messages[messages.length - 1];
const { content, uniqueProjectName } = await workbenchStore.generateZip();
const buf = await content.arrayBuffer();
const contentBase64 = uint8ArrayToBase64(new Uint8Array(buf));
saveProjectPrompt(lastMessage.id, { content: contentBase64, uniqueProjectName, input: _input });
};

/**
Expand Down
5 changes: 3 additions & 2 deletions app/components/chat/ImportFolderButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState } from 'react';
import type { Message } from 'ai';
import { toast } from 'react-toastify';
import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '~/utils/fileUtils';
import { createChatFromFolder } from '~/utils/folderImport';
import { createChatFromFolder, getFileArtifacts } from '~/utils/folderImport';
import { logStore } from '~/lib/stores/logs'; // Assuming logStore is imported from this location

interface ImportFolderButtonProps {
Expand Down Expand Up @@ -73,7 +73,8 @@ export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ classNam
toast.info(`Skipping ${binaryFilePaths.length} binary files`);
}

const messages = await createChatFromFolder(textFiles, binaryFilePaths, folderName);
const textFileArtifacts = await getFileArtifacts(textFiles);
const messages = await createChatFromFolder(textFileArtifacts, binaryFilePaths, folderName);

if (importChat) {
await importChat(folderName, [...messages]);
Expand Down
114 changes: 114 additions & 0 deletions app/components/chat/LoadProblemButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useState } from 'react';
import type { Message } from 'ai';
import { toast } from 'react-toastify';
import { createChatFromFolder, type FileArtifact } from '~/utils/folderImport';
import { logStore } from '~/lib/stores/logs'; // Assuming logStore is imported from this location
import { sendCommandDedicatedClient } from '../workbench/ReplayProtocolClient';
import type { BoltProblem } from './Messages.client';
import JSZip from 'jszip';

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

export const LoadProblemButton: React.FC<LoadProblemButtonProps> = ({ className, importChat }) => {
const [isLoading, setIsLoading] = useState(false);
const [isInputOpen, setIsInputOpen] = useState(false);

const handleSubmit = async (e: React.ChangeEvent<HTMLInputElement>) => {
setIsLoading(true);
setIsInputOpen(false);

const problemId = (document.getElementById('problem-input') as HTMLInputElement)?.value;

let problem: BoltProblem | null = null;
try {
const rv = await sendCommandDedicatedClient({
method: "Recording.globalExperimentalCommand",
params: {
name: "fetchBoltProblem",
params: { problemId },
},
});
console.log("FetchProblemRval", rv);
problem = (rv as any).rval.problem;
} catch (error) {
console.error("Error fetching problem", error);
toast.error("Failed to fetch problem");
}

if (!problem) {
return;
}

console.log("Problem", problem);

const zip = new JSZip();
await zip.loadAsync(problem.prompt.content, { base64: true });

const fileArtifacts: FileArtifact[] = [];
for (const [key, object] of Object.entries(zip.files)) {
if (object.dir) continue;
fileArtifacts.push({
content: await object.async('text'),
path: key,
});
}

const folderName = problem.prompt.uniqueProjectName;

try {
const messages = await createChatFromFolder(fileArtifacts, [], folderName);

if (importChat) {
await importChat(folderName, [...messages]);
}

logStore.logSystem('Problem loaded successfully', {
problemId,
textFileCount: fileArtifacts.length,
});
toast.success('Problem loaded successfully');
} catch (error) {
logStore.logError('Failed to load problem', error);
console.error('Failed to load problem:', error);
toast.error('Failed to load problem');
} finally {
setIsLoading(false);
}
};

return (
<>
{isInputOpen && (
<input
id="problem-input"
type="text"
webkitdirectory=""
directory=""
onChange={() => {}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSubmit(e as any);
}
}}
className="border border-gray-300 rounded px-2 py-1"
{...({} as any)}
/>
)}
{!isInputOpen && (
<button
onClick={() => {
setIsInputOpen(true);
}}
className={className}
disabled={isLoading}
>
<div className="i-ph:globe" />
{isLoading ? 'Loading...' : 'Load Problem'}
</button>
)}
</>
);
};
Loading

0 comments on commit c3e1764

Please sign in to comment.