fix: added more controlled rate for code streaming
Browse files- app/lib/stores/workbench.ts +14 -3
- app/utils/sampler.ts +49 -0
    	
        app/lib/stores/workbench.ts
    CHANGED
    
    | @@ -16,6 +16,7 @@ import * as nodePath from 'node:path'; | |
| 16 | 
             
            import { extractRelativePath } from '~/utils/diff';
         | 
| 17 | 
             
            import { description } from '~/lib/persistence';
         | 
| 18 | 
             
            import Cookies from 'js-cookie';
         | 
|  | |
| 19 |  | 
| 20 | 
             
            export interface ArtifactState {
         | 
| 21 | 
             
              id: string;
         | 
| @@ -262,9 +263,9 @@ export class WorkbenchStore { | |
| 262 | 
             
                this.artifacts.setKey(messageId, { ...artifact, ...state });
         | 
| 263 | 
             
              }
         | 
| 264 | 
             
              addAction(data: ActionCallbackData) {
         | 
| 265 | 
            -
                this._addAction(data);
         | 
| 266 |  | 
| 267 | 
            -
                 | 
| 268 | 
             
              }
         | 
| 269 | 
             
              async _addAction(data: ActionCallbackData) {
         | 
| 270 | 
             
                const { messageId } = data;
         | 
| @@ -280,7 +281,7 @@ export class WorkbenchStore { | |
| 280 |  | 
| 281 | 
             
              runAction(data: ActionCallbackData, isStreaming: boolean = false) {
         | 
| 282 | 
             
                if (isStreaming) {
         | 
| 283 | 
            -
                  this. | 
| 284 | 
             
                } else {
         | 
| 285 | 
             
                  this.addToExecutionQueue(() => this._runAction(data, isStreaming));
         | 
| 286 | 
             
                }
         | 
| @@ -294,6 +295,12 @@ export class WorkbenchStore { | |
| 294 | 
             
                  unreachable('Artifact not found');
         | 
| 295 | 
             
                }
         | 
| 296 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 297 | 
             
                if (data.action.type === 'file') {
         | 
| 298 | 
             
                  const wc = await webcontainer;
         | 
| 299 | 
             
                  const fullPath = nodePath.join(wc.workdir, data.action.filePath);
         | 
| @@ -323,6 +330,10 @@ export class WorkbenchStore { | |
| 323 | 
             
                }
         | 
| 324 | 
             
              }
         | 
| 325 |  | 
|  | |
|  | |
|  | |
|  | |
| 326 | 
             
              #getArtifact(id: string) {
         | 
| 327 | 
             
                const artifacts = this.artifacts.get();
         | 
| 328 | 
             
                return artifacts[id];
         | 
|  | |
| 16 | 
             
            import { extractRelativePath } from '~/utils/diff';
         | 
| 17 | 
             
            import { description } from '~/lib/persistence';
         | 
| 18 | 
             
            import Cookies from 'js-cookie';
         | 
| 19 | 
            +
            import { createSampler } from '~/utils/sampler';
         | 
| 20 |  | 
| 21 | 
             
            export interface ArtifactState {
         | 
| 22 | 
             
              id: string;
         | 
|  | |
| 263 | 
             
                this.artifacts.setKey(messageId, { ...artifact, ...state });
         | 
| 264 | 
             
              }
         | 
| 265 | 
             
              addAction(data: ActionCallbackData) {
         | 
| 266 | 
            +
                // this._addAction(data);
         | 
| 267 |  | 
| 268 | 
            +
                this.addToExecutionQueue(() => this._addAction(data));
         | 
| 269 | 
             
              }
         | 
| 270 | 
             
              async _addAction(data: ActionCallbackData) {
         | 
| 271 | 
             
                const { messageId } = data;
         | 
|  | |
| 281 |  | 
| 282 | 
             
              runAction(data: ActionCallbackData, isStreaming: boolean = false) {
         | 
| 283 | 
             
                if (isStreaming) {
         | 
| 284 | 
            +
                  this.actionStreamSampler(data, isStreaming);
         | 
| 285 | 
             
                } else {
         | 
| 286 | 
             
                  this.addToExecutionQueue(() => this._runAction(data, isStreaming));
         | 
| 287 | 
             
                }
         | 
|  | |
| 295 | 
             
                  unreachable('Artifact not found');
         | 
| 296 | 
             
                }
         | 
| 297 |  | 
| 298 | 
            +
                const action = artifact.runner.actions.get()[data.actionId];
         | 
| 299 | 
            +
             | 
| 300 | 
            +
                if (!action || action.executed) {
         | 
| 301 | 
            +
                  return;
         | 
| 302 | 
            +
                }
         | 
| 303 | 
            +
             | 
| 304 | 
             
                if (data.action.type === 'file') {
         | 
| 305 | 
             
                  const wc = await webcontainer;
         | 
| 306 | 
             
                  const fullPath = nodePath.join(wc.workdir, data.action.filePath);
         | 
|  | |
| 330 | 
             
                }
         | 
| 331 | 
             
              }
         | 
| 332 |  | 
| 333 | 
            +
              actionStreamSampler = createSampler(async (data: ActionCallbackData, isStreaming: boolean = false) => {
         | 
| 334 | 
            +
                return await this._runAction(data, isStreaming);
         | 
| 335 | 
            +
              }, 100); // TODO: remove this magic number to have it configurable
         | 
| 336 | 
            +
             | 
| 337 | 
             
              #getArtifact(id: string) {
         | 
| 338 | 
             
                const artifacts = this.artifacts.get();
         | 
| 339 | 
             
                return artifacts[id];
         | 
    	
        app/utils/sampler.ts
    ADDED
    
    | @@ -0,0 +1,49 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            /**
         | 
| 2 | 
            +
             * Creates a function that samples calls at regular intervals and captures trailing calls.
         | 
| 3 | 
            +
             * - Drops calls that occur between sampling intervals
         | 
| 4 | 
            +
             * - Takes one call per sampling interval if available
         | 
| 5 | 
            +
             * - Captures the last call if no call was made during the interval
         | 
| 6 | 
            +
             *
         | 
| 7 | 
            +
             * @param fn The function to sample
         | 
| 8 | 
            +
             * @param sampleInterval How often to sample calls (in ms)
         | 
| 9 | 
            +
             * @returns The sampled function
         | 
| 10 | 
            +
             */
         | 
| 11 | 
            +
            export function createSampler<T extends (...args: any[]) => any>(fn: T, sampleInterval: number): T {
         | 
| 12 | 
            +
              let lastArgs: Parameters<T> | null = null;
         | 
| 13 | 
            +
              let lastTime = 0;
         | 
| 14 | 
            +
              let timeout: NodeJS.Timeout | null = null;
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              // Create a function with the same type as the input function
         | 
| 17 | 
            +
              const sampled = function (this: any, ...args: Parameters<T>) {
         | 
| 18 | 
            +
                const now = Date.now();
         | 
| 19 | 
            +
                lastArgs = args;
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                // If we're within the sample interval, just store the args
         | 
| 22 | 
            +
                if (now - lastTime < sampleInterval) {
         | 
| 23 | 
            +
                  // Set up trailing call if not already set
         | 
| 24 | 
            +
                  if (!timeout) {
         | 
| 25 | 
            +
                    timeout = setTimeout(
         | 
| 26 | 
            +
                      () => {
         | 
| 27 | 
            +
                        timeout = null;
         | 
| 28 | 
            +
                        lastTime = Date.now();
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                        if (lastArgs) {
         | 
| 31 | 
            +
                          fn.apply(this, lastArgs);
         | 
| 32 | 
            +
                          lastArgs = null;
         | 
| 33 | 
            +
                        }
         | 
| 34 | 
            +
                      },
         | 
| 35 | 
            +
                      sampleInterval - (now - lastTime),
         | 
| 36 | 
            +
                    );
         | 
| 37 | 
            +
                  }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  return;
         | 
| 40 | 
            +
                }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                // If we're outside the interval, execute immediately
         | 
| 43 | 
            +
                lastTime = now;
         | 
| 44 | 
            +
                fn.apply(this, args);
         | 
| 45 | 
            +
                lastArgs = null;
         | 
| 46 | 
            +
              } as T;
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              return sampled;
         | 
| 49 | 
            +
            }
         | 
