diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index f33114739..f7fe80a08 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -24,16 +24,16 @@ const EXAMPLE_PROMPTS = [ { text: 'How do I center a div?' }, ]; -const providerList = [...new Set(MODEL_LIST.map((model) => model.provider))] +const providerList = [...new Set(MODEL_LIST.map((model) => model.provider))]; const ModelSelector = ({ model, setModel, provider, setProvider, modelList, providerList }) => { return (
-
); @@ -81,10 +83,10 @@ interface BaseChatProps { enhancingPrompt?: boolean; promptEnhanced?: boolean; input?: string; - model: string; - setModel: (model: string) => void; - provider: string; - setProvider: (provider: string) => void; + model?: string; + setModel?: (model: string) => void; + provider?: string; + setProvider?: (provider: string) => void; handleStop?: () => void; sendMessage?: (event: React.UIEvent, messageInput?: string) => void; handleInputChange?: (event: React.ChangeEvent) => void; @@ -144,7 +146,7 @@ export const BaseChat = React.forwardRef( expires: 30, // 30 days secure: true, // Only send over HTTPS sameSite: 'strict', // Protect against CSRF - path: '/' // Accessible across the site + path: '/', // Accessible across the site }); } catch (error) { console.error('Error saving API keys to cookies:', error); @@ -281,7 +283,9 @@ export const BaseChat = React.forwardRef( {input.length > 3 ? (
- Use Shift + Return for a new line + Use Shift +{' '} + Return for + a new line
) : null} @@ -315,4 +319,4 @@ export const BaseChat = React.forwardRef( ); }, -); \ No newline at end of file +); diff --git a/app/lib/hooks/useMessageParser.ts b/app/lib/hooks/useMessageParser.ts index a70fb82f4..97a063da5 100644 --- a/app/lib/hooks/useMessageParser.ts +++ b/app/lib/hooks/useMessageParser.ts @@ -36,6 +36,10 @@ const messageParser = new StreamingMessageParser({ workbenchStore.runAction(data); }, + onActionStream: (data) => { + logger.trace('onActionStream', data.action); + workbenchStore.runAction(data, true); + }, }, }); diff --git a/app/lib/runtime/action-runner.ts b/app/lib/runtime/action-runner.ts index e65911018..f94390be9 100644 --- a/app/lib/runtime/action-runner.ts +++ b/app/lib/runtime/action-runner.ts @@ -77,7 +77,7 @@ export class ActionRunner { }); } - async runAction(data: ActionCallbackData) { + async runAction(data: ActionCallbackData, isStreaming: boolean = false) { const { actionId } = data; const action = this.actions.get()[actionId]; @@ -88,19 +88,22 @@ export class ActionRunner { if (action.executed) { return; } + if (isStreaming && action.type !== 'file') { + return; + } - this.#updateAction(actionId, { ...action, ...data.action, executed: true }); + this.#updateAction(actionId, { ...action, ...data.action, executed: !isStreaming }); this.#currentExecutionPromise = this.#currentExecutionPromise .then(() => { - return this.#executeAction(actionId); + return this.#executeAction(actionId, isStreaming); }) .catch((error) => { console.error('Action failed:', error); }); } - async #executeAction(actionId: string) { + async #executeAction(actionId: string, isStreaming: boolean = false) { const action = this.actions.get()[actionId]; this.#updateAction(actionId, { status: 'running' }); @@ -121,7 +124,7 @@ export class ActionRunner { } } - this.#updateAction(actionId, { status: action.abortSignal.aborted ? 'aborted' : 'complete' }); + this.#updateAction(actionId, { status: isStreaming ? 'running' : action.abortSignal.aborted ? 'aborted' : 'complete' }); } catch (error) { this.#updateAction(actionId, { status: 'failed', error: 'Action failed' }); logger.error(`[${action.type}]:Action failed\n\n`, error); diff --git a/app/lib/runtime/message-parser.ts b/app/lib/runtime/message-parser.ts index 070841cb6..4b564da16 100644 --- a/app/lib/runtime/message-parser.ts +++ b/app/lib/runtime/message-parser.ts @@ -28,6 +28,7 @@ export interface ParserCallbacks { onArtifactOpen?: ArtifactCallback; onArtifactClose?: ArtifactCallback; onActionOpen?: ActionCallback; + onActionStream?: ActionCallback; onActionClose?: ActionCallback; } @@ -118,6 +119,21 @@ export class StreamingMessageParser { i = closeIndex + ARTIFACT_ACTION_TAG_CLOSE.length; } else { + if ('type' in currentAction && currentAction.type === 'file') { + let content = input.slice(i); + + this._options.callbacks?.onActionStream?.({ + artifactId: currentArtifact.id, + messageId, + actionId: String(state.actionId - 1), + action: { + ...currentAction as FileAction, + content, + filePath: currentAction.filePath, + }, + + }); + } break; } } else { diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index b71f35fe4..8589391c8 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -11,7 +11,8 @@ import { PreviewsStore } from './previews'; import { TerminalStore } from './terminal'; import JSZip from 'jszip'; import { saveAs } from 'file-saver'; -import { Octokit } from "@octokit/rest"; +import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest"; +import * as nodePath from 'node:path'; import type { WebContainerProcess } from '@webcontainer/api'; export interface ArtifactState { @@ -267,7 +268,7 @@ export class WorkbenchStore { artifact.runner.addAction(data); } - async runAction(data: ActionCallbackData) { + async runAction(data: ActionCallbackData, isStreaming: boolean = false) { const { messageId } = data; const artifact = this.#getArtifact(messageId); @@ -275,8 +276,29 @@ export class WorkbenchStore { if (!artifact) { unreachable('Artifact not found'); } + if (data.action.type === 'file') { + let wc = await webcontainer + const fullPath = nodePath.join(wc.workdir, data.action.filePath); + if (this.selectedFile.value !== fullPath) { + this.setSelectedFile(fullPath); + } + if (this.currentView.value !== 'code') { + this.currentView.set('code'); + } + const doc = this.#editorStore.documents.get()[fullPath]; + if (!doc) { + await artifact.runner.runAction(data, isStreaming); + } - artifact.runner.runAction(data); + this.#editorStore.updateFile(fullPath, data.action.content); + + if (!isStreaming) { + this.resetCurrentDocument(); + await artifact.runner.runAction(data); + } + } else { + artifact.runner.runAction(data); + } } #getArtifact(id: string) { @@ -360,9 +382,10 @@ export class WorkbenchStore { const octokit = new Octokit({ auth: githubToken }); // Check if the repository already exists before creating it - let repo + let repo: RestEndpointMethodTypes["repos"]["get"]["response"]['data'] try { - repo = await octokit.repos.get({ owner: owner, repo: repoName }); + let resp = await octokit.repos.get({ owner: owner, repo: repoName }); + repo = resp.data } catch (error) { if (error instanceof Error && 'status' in error && error.status === 404) { // Repository doesn't exist, so create a new one diff --git a/package.json b/package.json index f0f25e63f..ce8e95d0d 100644 --- a/package.json +++ b/package.json @@ -117,5 +117,5 @@ "resolutions": { "@typescript-eslint/utils": "^8.0.0-alpha.30" }, - "packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228" + "packageManager": "pnpm@9.4.0" }