Skip to content

Commit c832b88

Browse files
committed
further improve the style
1 parent a2b1ed3 commit c832b88

File tree

4 files changed

+97
-66
lines changed

4 files changed

+97
-66
lines changed

frontend/src/app/workspace/component/copilot-chat/copilot-chat.component.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@
4040
</nz-switch>
4141
<span class="toggle-label">Show Results</span>
4242
<div class="chat-header-buttons">
43+
<button
44+
*ngIf="isProcessing"
45+
nz-button
46+
nzType="text"
47+
nzSize="small"
48+
(click)="stopGeneration()"
49+
nz-tooltip
50+
nzTooltipTitle="Stop generation">
51+
<i
52+
nz-icon
53+
nzType="stop"
54+
nzTheme="fill"></i>
55+
</button>
4356
<button
4457
nz-button
4558
nzType="text"

frontend/src/app/workspace/component/copilot-chat/copilot-chat.component.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,14 @@ export class CopilotChatComponent implements OnDestroy {
211211
this.isExpanded = !this.isExpanded;
212212
}
213213

214+
/**
215+
* Stop the current generation
216+
*/
217+
public stopGeneration(): void {
218+
this.copilotService.stopGeneration();
219+
this.isProcessing = false;
220+
}
221+
214222
/**
215223
* Get the copilot coeditor object (for displaying in UI)
216224
*/

frontend/src/app/workspace/service/copilot/texera-copilot.ts

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ export class TexeraCopilot {
8989
// Message history using AI SDK's ModelMessage type
9090
private messages: ModelMessage[] = [];
9191

92+
// AbortController for stopping generation
93+
private currentAbortController?: AbortController;
94+
9295
constructor(
9396
private workflowActionService: WorkflowActionService,
9497
private workflowUtilService: WorkflowUtilService,
@@ -189,6 +192,9 @@ export class TexeraCopilot {
189192
return;
190193
}
191194

195+
// Create new AbortController for this generation
196+
this.currentAbortController = new AbortController();
197+
192198
// 1) push the user message (don't emit to stream - already handled by UI)
193199
const userMessage: UserModelMessage = { role: "user", content: message };
194200
this.messages.push(userMessage);
@@ -201,12 +207,19 @@ export class TexeraCopilot {
201207
model: this.model,
202208
messages: this.messages, // full history
203209
tools,
210+
abortSignal: this.currentAbortController.signal,
204211
system:
205212
"You are Texera Copilot, an AI assistant for building and modifying data workflows. " +
206213
"Your task is helping user explore the data using operators. " +
207214
"Common operators would be Limit to limit the size of data; " +
208215
"Aggregate to do some aggregation; and some visualization operator. " +
209-
"A good generation style is adding an operator, configuring its property and then executing it to make sure each editing is valid. Generate 3-5 operators is enough for every round of generation",
216+
"A good generation style is, " +
217+
"1. listing what operators are available" +
218+
"2. add the operator" +
219+
"3. retrieve the OperatorSchema" +
220+
"4. Configuring its property based on the schema" +
221+
"5. executing it to make sure each editing is valid. " +
222+
"Generate 3-5 operators is enough for every round of generation",
210223
stopWhen: stepCountIs(50),
211224

212225
// optional: observe every completed step (tool calls + results available)
@@ -241,6 +254,9 @@ export class TexeraCopilot {
241254
console.log(`Agent loop finished in ${steps.length} step(s), ${totalToolCalls} tool call(s).`);
242255
}
243256

257+
// Clear all copilot presence indicators when generation completes
258+
this.copilotCoeditorService.clearAll();
259+
244260
// Emit final response with raw data
245261
const finalResponse: AgentResponse = {
246262
type: "response",
@@ -251,10 +267,21 @@ export class TexeraCopilot {
251267
observer.complete();
252268
})
253269
.catch((err: any) => {
254-
const errorText = `Error: ${err?.message ?? String(err)}`;
255-
const assistantError: AssistantModelMessage = { role: "assistant", content: errorText };
256-
this.messages.push(assistantError);
257-
observer.error(err);
270+
// Clear all copilot presence indicators on error
271+
this.copilotCoeditorService.clearAll();
272+
273+
// Check if error is due to user abort
274+
if (err?.name === "AbortError" || err?.message?.includes("aborted")) {
275+
console.log("Generation stopped by user");
276+
// Just complete the observable without adding error message to history
277+
observer.complete();
278+
} else {
279+
// For real errors, add to message history and propagate error
280+
const errorText = `Error: ${err?.message ?? String(err)}`;
281+
const assistantError: AssistantModelMessage = { role: "assistant", content: errorText };
282+
this.messages.push(assistantError);
283+
observer.error(err);
284+
}
258285
});
259286
});
260287
}
@@ -347,10 +374,24 @@ export class TexeraCopilot {
347374
this.messages.push(message);
348375
}
349376

377+
/**
378+
* Stop the current generation without clearing messages
379+
*/
380+
public stopGeneration(): void {
381+
if (this.currentAbortController) {
382+
this.currentAbortController.abort();
383+
this.currentAbortController = undefined;
384+
console.log("Generation stopped");
385+
}
386+
}
387+
350388
/**
351389
* Disconnect and cleanup copilot resources
352390
*/
353391
public async disconnect(): Promise<void> {
392+
// Stop any ongoing generation
393+
this.stopGeneration();
394+
354395
// Disconnect the MCP client if it exists
355396
if (this.mcpClient) {
356397
await this.mcpClient.close();

0 commit comments

Comments
 (0)