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 |
+
}
|