Skip to content

Commit 4b0f599

Browse files
committed
Add ability to stop automodeling
1 parent 4f71262 commit 4b0f599

File tree

8 files changed

+94
-10
lines changed

8 files changed

+94
-10
lines changed

extensions/ql-vscode/src/common/interface-types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,11 @@ interface GenerateExternalApiFromLlmMessage {
554554
modeledMethods: Record<string, ModeledMethod>;
555555
}
556556

557+
interface StopGeneratingExternalApiFromLlmMessage {
558+
t: "stopGeneratingExternalApiFromLlm";
559+
dependencyName: string;
560+
}
561+
557562
interface ModelDependencyMessage {
558563
t: "modelDependency";
559564
}
@@ -575,4 +580,5 @@ export type FromDataExtensionsEditorMessage =
575580
| SaveModeledMethods
576581
| GenerateExternalApiMessage
577582
| GenerateExternalApiFromLlmMessage
583+
| StopGeneratingExternalApiFromLlmMessage
578584
| ModelDependencyMessage;

extensions/ql-vscode/src/data-extensions-editor/auto-model-codeml-queries.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ type AutoModelQueriesOptions = {
160160
queryStorageDir: string;
161161

162162
progress: ProgressCallback;
163+
cancellationTokenSource: CancellationTokenSource;
163164
};
164165

165166
export type AutoModelQueriesResult = {
@@ -174,12 +175,11 @@ export async function runAutoModelQueries({
174175
databaseItem,
175176
queryStorageDir,
176177
progress,
178+
cancellationTokenSource,
177179
}: AutoModelQueriesOptions): Promise<AutoModelQueriesResult | undefined> {
178180
// maxStep for this part is 1500
179181
const maxStep = 1500;
180182

181-
const cancellationTokenSource = new CancellationTokenSource();
182-
183183
const qlpack = await qlpackOfDatabase(cliServer, databaseItem);
184184

185185
// CodeQL needs to have access to the database to be able to retrieve the

extensions/ql-vscode/src/data-extensions-editor/auto-modeler.ts

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ import { CodeQLCliServer } from "../codeql-cli/cli";
1515
import { QueryRunner } from "../query-server";
1616
import { DatabaseItem } from "../databases/local-databases";
1717
import { Mode } from "./shared/mode";
18+
import { CancellationTokenSource } from "vscode";
1819

1920
// Limit the number of candidates we send to the model in each request
2021
// to avoid long requests.
2122
// Note that the model may return fewer than this number of candidates.
2223
const candidateBatchSize = 20;
2324

2425
export class AutoModeler {
26+
private readonly jobs: Map<string, CancellationTokenSource>;
27+
2528
constructor(
2629
private readonly app: App,
2730
private readonly cliServer: CodeQLCliServer,
@@ -34,27 +37,56 @@ export class AutoModeler {
3437
private readonly addModeledMethods: (
3538
modeledMethods: Record<string, ModeledMethod>,
3639
) => Promise<void>,
37-
) {}
40+
) {
41+
this.jobs = new Map<string, CancellationTokenSource>();
42+
}
3843

3944
public async startModeling(
4045
dependency: string,
4146
externalApiUsages: ExternalApiUsage[],
4247
modeledMethods: Record<string, ModeledMethod>,
4348
mode: Mode,
4449
): Promise<void> {
45-
await this.modelDependency(
46-
dependency,
47-
externalApiUsages,
48-
modeledMethods,
49-
mode,
50-
);
50+
if (this.jobs.has(dependency)) {
51+
return;
52+
}
53+
54+
const cancellationTokenSource = new CancellationTokenSource();
55+
this.jobs.set(dependency, cancellationTokenSource);
56+
57+
try {
58+
await this.modelDependency(
59+
dependency,
60+
externalApiUsages,
61+
modeledMethods,
62+
mode,
63+
cancellationTokenSource,
64+
);
65+
} finally {
66+
this.jobs.delete(dependency);
67+
}
68+
}
69+
70+
public async stopModeling(dependency: string): Promise<void> {
71+
void extLogger.log(`Stopping modeling for dependency ${dependency}`);
72+
const cancellationTokenSource = this.jobs.get(dependency);
73+
if (cancellationTokenSource) {
74+
cancellationTokenSource.cancel();
75+
}
76+
}
77+
78+
public async stopAllModeling(): Promise<void> {
79+
for (const cancellationTokenSource of this.jobs.values()) {
80+
cancellationTokenSource.cancel();
81+
}
5182
}
5283

5384
private async modelDependency(
5485
dependency: string,
5586
externalApiUsages: ExternalApiUsage[],
5687
modeledMethods: Record<string, ModeledMethod>,
5788
mode: Mode,
89+
cancellationTokenSource: CancellationTokenSource,
5890
): Promise<void> {
5991
void extLogger.log(`Modeling dependency ${dependency}`);
6092
await withProgress(async (progress) => {
@@ -79,6 +111,10 @@ export class AutoModeler {
79111
);
80112
try {
81113
for (let i = 0; i < batchNumber; i++) {
114+
if (cancellationTokenSource.token.isCancellationRequested) {
115+
break;
116+
}
117+
82118
const start = i * candidateBatchSize;
83119
const end = start + candidateBatchSize;
84120
const candidatesToProcess = allCandidateMethods.slice(start, end);
@@ -100,6 +136,7 @@ export class AutoModeler {
100136
mode,
101137
progress,
102138
maxStep,
139+
cancellationTokenSource,
103140
);
104141
}
105142
} finally {
@@ -114,6 +151,7 @@ export class AutoModeler {
114151
mode: Mode,
115152
progress: ProgressCallback,
116153
maxStep: number,
154+
cancellationTokenSource: CancellationTokenSource,
117155
): Promise<void> {
118156
const usages = await runAutoModelQueries({
119157
mode,
@@ -123,6 +161,7 @@ export class AutoModeler {
123161
queryStorageDir: this.queryStorageDir,
124162
databaseItem: this.databaseItem,
125163
progress: (update) => progress({ ...update, maxStep }),
164+
cancellationTokenSource,
126165
});
127166
if (!usages) {
128167
return;

extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ export class DataExtensionsEditorView extends AbstractWebview<
187187
);
188188
}
189189
break;
190+
case "stopGeneratingExternalApiFromLlm":
191+
await this.autoModeler.stopModeling(msg.dependencyName);
192+
break;
190193
case "modelDependency":
191194
await this.modelDependency();
192195
break;

extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,13 @@ export function DataExtensionsEditor({
241241
[],
242242
);
243243

244+
const onStopGenerateFromLlmClick = useCallback((dependencyName: string) => {
245+
vscode.postMessage({
246+
t: "stopGeneratingExternalApiFromLlm",
247+
dependencyName,
248+
});
249+
}, []);
250+
244251
const onOpenDatabaseClick = useCallback(() => {
245252
vscode.postMessage({
246253
t: "openDatabase",
@@ -345,6 +352,7 @@ export function DataExtensionsEditor({
345352
onChange={onChange}
346353
onSaveModelClick={onSaveModelClick}
347354
onGenerateFromLlmClick={onGenerateFromLlmClick}
355+
onStopGenerateFromLlmClick={onStopGenerateFromLlmClick}
348356
onGenerateFromSourceClick={onGenerateFromSourceClick}
349357
onModelDependencyClick={onModelDependencyClick}
350358
/>

extensions/ql-vscode/src/view/data-extensions-editor/LibraryRow.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ type Props = {
8989
externalApiUsages: ExternalApiUsage[],
9090
modeledMethods: Record<string, ModeledMethod>,
9191
) => void;
92+
onStopGenerateFromLlmClick: (dependencyName: string) => void;
9293
onGenerateFromSourceClick: () => void;
9394
onModelDependencyClick: () => void;
9495
};
@@ -105,6 +106,7 @@ export const LibraryRow = ({
105106
onChange,
106107
onSaveModelClick,
107108
onGenerateFromLlmClick,
109+
onStopGenerateFromLlmClick,
108110
onGenerateFromSourceClick,
109111
onModelDependencyClick,
110112
}: Props) => {
@@ -127,6 +129,15 @@ export const LibraryRow = ({
127129
[title, externalApiUsages, modeledMethods, onGenerateFromLlmClick],
128130
);
129131

132+
const handleStopModelWithAI = useCallback(
133+
async (e: React.MouseEvent) => {
134+
onStopGenerateFromLlmClick(title);
135+
e.stopPropagation();
136+
e.preventDefault();
137+
},
138+
[title, onStopGenerateFromLlmClick],
139+
);
140+
130141
const handleModelFromSource = useCallback(
131142
async (e: React.MouseEvent) => {
132143
onGenerateFromSourceClick();
@@ -167,6 +178,12 @@ export const LibraryRow = ({
167178
);
168179
}, [externalApiUsages, modifiedSignatures]);
169180

181+
const canStopAutoModeling = useMemo(() => {
182+
return externalApiUsages.some((externalApiUsage) =>
183+
inProgressSignatures.has(externalApiUsage.signature),
184+
);
185+
}, [externalApiUsages, inProgressSignatures]);
186+
170187
return (
171188
<LibraryContainer>
172189
<TitleContainer onClick={toggleExpanded} aria-expanded={isExpanded}>
@@ -185,12 +202,18 @@ export const LibraryRow = ({
185202
</ModeledPercentage>
186203
{hasUnsavedChanges ? <VSCodeTag>UNSAVED</VSCodeTag> : null}
187204
</NameContainer>
188-
{viewState.showLlmButton && (
205+
{viewState.showLlmButton && !canStopAutoModeling && (
189206
<VSCodeButton appearance="icon" onClick={handleModelWithAI}>
190207
<Codicon name="lightbulb-autofix" label="Model with AI" />
191208
&nbsp;Model with AI
192209
</VSCodeButton>
193210
)}
211+
{viewState.showLlmButton && canStopAutoModeling && (
212+
<VSCodeButton appearance="icon" onClick={handleStopModelWithAI}>
213+
<Codicon name="debug-stop" label="Stop model with AI" />
214+
&nbsp;Stop
215+
</VSCodeButton>
216+
)}
194217
{viewState.mode === Mode.Application && (
195218
<VSCodeButton appearance="icon" onClick={handleModelFromSource}>
196219
<Codicon name="code" label="Model from source" />

extensions/ql-vscode/src/view/data-extensions-editor/ModeledMethodsList.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type Props = {
3131
externalApiUsages: ExternalApiUsage[],
3232
modeledMethods: Record<string, ModeledMethod>,
3333
) => void;
34+
onStopGenerateFromLlmClick: (dependencyName: string) => void;
3435
onGenerateFromSourceClick: () => void;
3536
onModelDependencyClick: () => void;
3637
};
@@ -49,6 +50,7 @@ export const ModeledMethodsList = ({
4950
onChange,
5051
onSaveModelClick,
5152
onGenerateFromLlmClick,
53+
onStopGenerateFromLlmClick,
5254
onGenerateFromSourceClick,
5355
onModelDependencyClick,
5456
}: Props) => {
@@ -93,6 +95,7 @@ export const ModeledMethodsList = ({
9395
onChange={onChange}
9496
onSaveModelClick={onSaveModelClick}
9597
onGenerateFromLlmClick={onGenerateFromLlmClick}
98+
onStopGenerateFromLlmClick={onStopGenerateFromLlmClick}
9699
onGenerateFromSourceClick={onGenerateFromSourceClick}
97100
onModelDependencyClick={onModelDependencyClick}
98101
/>

extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/auto-model-codeml-queries.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { MethodSignature } from "../../../../src/data-extensions-editor/external
1919
import { join } from "path";
2020
import { exists, readFile } from "fs-extra";
2121
import { load as loadYaml } from "js-yaml";
22+
import { CancellationTokenSource } from "vscode-jsonrpc";
2223

2324
describe("runAutoModelQueries", () => {
2425
const qlpack = {
@@ -142,6 +143,7 @@ describe("runAutoModelQueries", () => {
142143
}),
143144
queryStorageDir: "/tmp/queries",
144145
progress: jest.fn(),
146+
cancellationTokenSource: new CancellationTokenSource(),
145147
};
146148

147149
const result = await runAutoModelQueries(options);

0 commit comments

Comments
 (0)