Skip to content

add agent thoughts#91

Open
wrenj wants to merge 1 commit into
mainfrom
thinking
Open

add agent thoughts#91
wrenj wants to merge 1 commit into
mainfrom
thinking

Conversation

@wrenj

@wrenj wrenj commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

@wrenj wrenj requested review from jgindin and sugoi-yuzuru July 13, 2026 22:21
@github-actions

Copy link
Copy Markdown
Contributor

⚡ A2UI Composer PR Preview

Your automated preview is successfully live:
👉 Launch PR Preview

Note: This environment will be wiped automatically when the PR is merged or closed.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for streaming and displaying the model's thought process ('thinking' blocks) and introduces the ability to abort active prompt streams. Key changes include UI updates for the thinking blocks, a toggleable stop/send button, and backend integration using AbortController to handle stream cancellation. The review feedback suggests simplifying the AutoScrollDirective by using an input setter instead of ngOnChanges, cleaning up several inline type imports in favor of top-level imports, and using a more robust object property check for AbortError in the standalone client.

Comment on lines +33 to +42
export class AutoScrollDirective implements OnChanges {
@Input('a2uiComposerAutoScroll') autoScroll: string | undefined = '';
private readonly el = inject(ElementRef);

ngOnChanges() {
requestAnimationFrame(() => {
this.el.nativeElement.scrollTop = this.el.nativeElement.scrollHeight;
});
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using an input setter is more idiomatic in Angular for simple input-triggered side effects. It allows you to remove the OnChanges interface implementation and the ngOnChanges lifecycle hook entirely, simplifying the class structure.

export class AutoScrollDirective {
  private readonly el = inject(ElementRef);

  @Input('a2uiComposerAutoScroll') set autoScroll(value: string | undefined) {
    requestAnimationFrame(() => {
      this.el.nativeElement.scrollTop = this.el.nativeElement.scrollHeight;
    });
  }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...but please use the input signal, and not the @Input directive.

];
}

private activeStreamResponse?: import('../llm-client/llm-client').LlmStreamResponse;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using inline imports for types that can be imported at the top of the file. Please add LlmStreamResponse to the existing imports from ../llm-client/llm-client at the top of the file.

Suggested change
private activeStreamResponse?: import('../llm-client/llm-client').LlmStreamResponse;
private activeStreamResponse?: LlmStreamResponse;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL

Comment on lines +126 to +128
const config: import('@google/genai').GenerateContentConfig = systemInstruction
? {systemInstruction}
: {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using inline imports for types that can be imported at the top of the file. Please add GenerateContentConfig to the imports from @google/genai at the top of the file.

Suggested change
const config: import('@google/genai').GenerateContentConfig = systemInstruction
? {systemInstruction}
: {};
const config: GenerateContentConfig = systemInstruction
? {systemInstruction}
: {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL

const responseStream = await ai.models.generateContentStream(params);

const buffer: string[] = [];
const buffer: import('./llm-client').LlmStreamChunk[] = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using inline imports for types that can be imported at the top of the file. Please add LlmStreamChunk to the imports from ./llm-client at the top of the file.

Suggested change
const buffer: import('./llm-client').LlmStreamChunk[] = [];
const buffer: LlmStreamChunk[] = [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL

Comment on lines +189 to +191
} catch (err: unknown) {
if (err instanceof Error && err.name === 'AbortError') {
isDone = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The instanceof Error check can sometimes be fragile or fail in certain environments (e.g., cross-realm/iframe environments or specific test runners) when dealing with DOMException or custom abort objects. Using a robust object property check (similar to the one used in chat-coordinator.ts) is safer and more consistent.

Suggested change
} catch (err: unknown) {
if (err instanceof Error && err.name === 'AbortError') {
isDone = true;
} catch (err: unknown) {
if (err && typeof err === 'object' && 'name' in err && err.name === 'AbortError') {
isDone = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL

Comment on lines +204 to +208
const contentStream: AsyncIterable<import('./llm-client').LlmStreamChunk> = {
[Symbol.asyncIterator]() {
let localBufferIndex = 0;
return {
async next(): Promise<IteratorResult<string>> {
async next(): Promise<IteratorResult<import('./llm-client').LlmStreamChunk>> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid using inline imports for types that can be imported at the top of the file. Please use the top-level LlmStreamChunk import here as well.

Suggested change
const contentStream: AsyncIterable<import('./llm-client').LlmStreamChunk> = {
[Symbol.asyncIterator]() {
let localBufferIndex = 0;
return {
async next(): Promise<IteratorResult<string>> {
async next(): Promise<IteratorResult<import('./llm-client').LlmStreamChunk>> {
const contentStream: AsyncIterable<LlmStreamChunk> = {
[Symbol.asyncIterator]() {
let localBufferIndex = 0;
return {
async next(): Promise<IteratorResult<LlmStreamChunk>> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL

Comment on lines +33 to +42
export class AutoScrollDirective implements OnChanges {
@Input('a2uiComposerAutoScroll') autoScroll: string | undefined = '';
private readonly el = inject(ElementRef);

ngOnChanges() {
requestAnimationFrame(() => {
this.el.nativeElement.scrollTop = this.el.nativeElement.scrollHeight;
});
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...but please use the input signal, and not the @Input directive.


.thinking-header {
padding: 6px 10px;
font-weight: 500;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure there's a material token that we should use here: font: var(--mat-sys-label-medium);

Note that this also removes the font-size property above (which is a good thing).

/**
* Aborts the currently active prompt stream.
*/
protected abortPrompt(): void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit of a nit, but I recommend cancelPrompt instead of abortPropmpt.

/**
* Aborts the currently active streaming request if there is one.
*/
abortActiveStream(): void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend changing all usages of abort to cancel

expect(streamResponse.complete).toBeDefined();

const collectedChunks: string[] = [];
const collectedChunks: import('./llm-client').LlmStreamChunk[] = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too (and below), don't use inline imports.

readonly thinking?: string;
}

export interface LlmStreamChunk {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need both the LlmStreamChunk and LlmResponse since they're the same?

If we want to know if something is the complete response or just a chunk, maybe a boolean flag to differentiate between the cases would be simpler?

notifyListeners();
} catch (err) {
} catch (err: unknown) {
if (err instanceof Error && err.name === 'AbortError') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking the name seems fragile.

At the very least, use a const here and where the Error is thrown from.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants