diff --git a/app/commit.json b/app/commit.json index 50d6492..2b7141c 100644 --- a/app/commit.json +++ b/app/commit.json @@ -1 +1 @@ -{ "commit": "a936c5a99036e0ea65c976e19b3bdb39f8d7cd40" } +{ "commit": "6ffcdd8b3c54e2560b327ca1b4f4eb33ba54c0db" } diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index 41df733..db4e435 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -436,12 +436,15 @@ export const BaseChat = React.forwardRef( } event.preventDefault(); - + if (isStreaming) { handleStop?.(); return; } - + // ignore if using input method engine + if (event.nativeEvent.isComposing) { + return + } handleSendMessage?.(event); } }} diff --git a/app/components/settings/connections/ConnectionsTab.tsx b/app/components/settings/connections/ConnectionsTab.tsx index 8338395..4fe43d9 100644 --- a/app/components/settings/connections/ConnectionsTab.tsx +++ b/app/components/settings/connections/ConnectionsTab.tsx @@ -15,6 +15,7 @@ export default function ConnectionsTab() { hasToken: !!githubToken, }); toast.success('GitHub credentials saved successfully!'); + Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' })); }; return ( diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index 068cc82..bbd537d 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -16,6 +16,7 @@ import * as nodePath from 'node:path'; import { extractRelativePath } from '~/utils/diff'; import { description } from '~/lib/persistence'; import Cookies from 'js-cookie'; +import { createSampler } from '~/utils/sampler'; export interface ArtifactState { id: string; @@ -280,7 +281,7 @@ export class WorkbenchStore { runAction(data: ActionCallbackData, isStreaming: boolean = false) { if (isStreaming) { - this._runAction(data, isStreaming); + this.actionStreamSampler(data, isStreaming); } else { this.addToExecutionQueue(() => this._runAction(data, isStreaming)); } @@ -296,7 +297,8 @@ export class WorkbenchStore { const action = artifact.runner.actions.get()[data.actionId]; - if (action.executed) { + + if (!action || action.executed) { return; } @@ -329,6 +331,10 @@ export class WorkbenchStore { } } + actionStreamSampler = createSampler(async (data: ActionCallbackData, isStreaming: boolean = false) => { + return await this._runAction(data, isStreaming); + }, 100); // TODO: remove this magic number to have it configurable + #getArtifact(id: string) { const artifacts = this.artifacts.get(); return artifacts[id]; diff --git a/app/utils/sampler.ts b/app/utils/sampler.ts new file mode 100644 index 0000000..9639909 --- /dev/null +++ b/app/utils/sampler.ts @@ -0,0 +1,49 @@ +/** + * Creates a function that samples calls at regular intervals and captures trailing calls. + * - Drops calls that occur between sampling intervals + * - Takes one call per sampling interval if available + * - Captures the last call if no call was made during the interval + * + * @param fn The function to sample + * @param sampleInterval How often to sample calls (in ms) + * @returns The sampled function + */ +export function createSampler any>(fn: T, sampleInterval: number): T { + let lastArgs: Parameters | null = null; + let lastTime = 0; + let timeout: NodeJS.Timeout | null = null; + + // Create a function with the same type as the input function + const sampled = function (this: any, ...args: Parameters) { + const now = Date.now(); + lastArgs = args; + + // If we're within the sample interval, just store the args + if (now - lastTime < sampleInterval) { + // Set up trailing call if not already set + if (!timeout) { + timeout = setTimeout( + () => { + timeout = null; + lastTime = Date.now(); + + if (lastArgs) { + fn.apply(this, lastArgs); + lastArgs = null; + } + }, + sampleInterval - (now - lastTime), + ); + } + + return; + } + + // If we're outside the interval, execute immediately + lastTime = now; + fn.apply(this, args); + lastArgs = null; + } as T; + + return sampled; +}