Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions apps/website/content/docs/chat/api/api-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@
"params": [],
"examples": [],
"properties": [
{
"name": "alignItems",
"type": "Signal<string>",
"description": "",
"optional": false
},
{
"name": "alignment",
"type": "InputSignal<ColumnAlignment>",
Expand All @@ -253,12 +259,6 @@
"description": "",
"optional": false
},
{
"name": "colClass",
"type": "Signal<string>",
"description": "",
"optional": false
},
{
"name": "distribution",
"type": "InputSignal<\"center\" | \"start\" | \"end\" | \"spaceBetween\" | \"spaceAround\" | \"spaceEvenly\">",
Expand All @@ -277,6 +277,12 @@
"description": "",
"optional": false
},
{
"name": "gapPx",
"type": "Signal<number>",
"description": "Convert the Tailwind gap unit (multiples of 4px) to pixels.",
"optional": false
},
{
"name": "loading",
"type": "InputSignal<boolean>",
Expand Down Expand Up @@ -595,7 +601,7 @@
},
{
"name": "listClass",
"type": "Signal<\"flex flex-row gap-1 overflow-x-auto\" | \"flex flex-col gap-1 overflow-y-auto max-h-96\">",
"type": "Signal<\"a2ui-list--horizontal\" | \"a2ui-list--vertical\">",
"description": "",
"optional": false
},
Expand Down Expand Up @@ -813,6 +819,12 @@
"params": [],
"examples": [],
"properties": [
{
"name": "alignItems",
"type": "Signal<string>",
"description": "",
"optional": false
},
{
"name": "alignment",
"type": "InputSignal<RowAlignment>",
Expand Down Expand Up @@ -850,17 +862,23 @@
"optional": false
},
{
"name": "loading",
"type": "InputSignal<boolean>",
"description": "",
"name": "gapPx",
"type": "Signal<number>",
"description": "Convert the gap unit (multiples of 4px) to pixels.",
"optional": false
},
{
"name": "rowClass",
"name": "justifyContent",
"type": "Signal<string>",
"description": "",
"optional": false
},
{
"name": "loading",
"type": "InputSignal<boolean>",
"description": "",
"optional": false
},
{
"name": "spec",
"type": "InputSignal<Spec>",
Expand Down
8 changes: 7 additions & 1 deletion libs/chat/src/lib/a2ui/catalog/audio-player.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import type { Spec } from '@json-render/core';
standalone: true,
template: `
<audio
class="w-full"
class="a2ui-audio"
[src]="url()"
[autoplay]="autoPlay()"
[controls]="controls()"
></audio>
`,
styles: [`
.a2ui-audio {
display: block;
width: 100%;
}
`],
})
export class A2uiAudioPlayerComponent {
readonly url = input<string>('');
Expand Down
31 changes: 27 additions & 4 deletions libs/chat/src/lib/a2ui/catalog/button.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import { RenderElementComponent } from '@ngaf/render';
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<button
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors inline-flex items-center justify-center"
[class]="primary()
? 'bg-blue-600 hover:bg-blue-700 text-white'
: 'bg-transparent border border-white/20 hover:bg-white/10 text-white/80'"
[class]="primary() ? 'a2ui-btn a2ui-btn--primary' : 'a2ui-btn a2ui-btn--secondary'"
[disabled]="disabled()"
(click)="handleClick()"
>
Expand All @@ -22,6 +19,32 @@ import { RenderElementComponent } from '@ngaf/render';
}
</button>
`,
styles: [`
.a2ui-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 120ms, opacity 120ms;
border: none;
}
.a2ui-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.a2ui-btn--primary {
background: var(--a2ui-primary, #2563eb);
color: #fff;
}
.a2ui-btn--primary:hover:not(:disabled) { background: var(--a2ui-primary-hover, #1d4ed8); }
.a2ui-btn--secondary {
background: transparent;
color: var(--a2ui-input-text, rgba(255,255,255,0.8));
border: 1px solid var(--a2ui-border, rgba(255,255,255,0.2));
}
.a2ui-btn--secondary:hover:not(:disabled) { background: rgba(255,255,255,0.08); }
`],
})
export class A2uiButtonComponent {
/** v1: child Text component is rendered inside the button via childKeys. */
Expand Down
13 changes: 12 additions & 1 deletion libs/chat/src/lib/a2ui/catalog/card.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,23 @@ import { RenderElementComponent } from '@ngaf/render';
standalone: true,
imports: [RenderElementComponent],
template: `
<div class="rounded-xl border border-white/10 bg-white/5 p-4 backdrop-blur-sm">
<div class="a2ui-card">
@for (key of childKeys(); track key) {
<render-element [elementKey]="key" [spec]="spec()" />
}
</div>
`,
styles: [`
.a2ui-card {
display: flex;
flex-direction: column;
gap: 8px;
border-radius: 12px;
border: 1px solid var(--a2ui-border, rgba(255,255,255,0.1));
background: var(--a2ui-card-bg, rgba(255,255,255,0.05));
padding: 16px;
}
`],
})
export class A2uiCardComponent {
/** v1: a single child key, delivered via childKeys[0] from the render framework. */
Expand Down
20 changes: 18 additions & 2 deletions libs/chat/src/lib/a2ui/catalog/check-box.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,27 @@ import { emitBinding } from './emit-binding';
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<label class="flex items-center gap-2 text-sm cursor-pointer">
<input type="checkbox" [checked]="checked()" (change)="onChange($event)" class="rounded" />
<label class="a2ui-cb">
<input type="checkbox" [checked]="checked()" (change)="onChange($event)" class="a2ui-cb__input" />
{{ label() }}
</label>
`,
styles: [`
.a2ui-cb {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
cursor: pointer;
}
.a2ui-cb__input {
width: 16px;
height: 16px;
border-radius: 4px;
cursor: pointer;
accent-color: var(--a2ui-primary, #2563eb);
}
`],
})
export class A2uiCheckBoxComponent {
readonly label = input<string>('');
Expand Down
21 changes: 14 additions & 7 deletions libs/chat/src/lib/a2ui/catalog/column.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,27 @@ import { RenderElementComponent } from '@ngaf/render';

type ColumnAlignment = 'start' | 'center' | 'end' | 'stretch';

const ALIGN_MAP: Record<ColumnAlignment, string> = {
start: 'flex-start', center: 'center', end: 'flex-end', stretch: 'stretch',
};

@Component({
selector: 'a2ui-column',
standalone: true,
imports: [RenderElementComponent],
template: `
<div [class]="colClass()">
<div class="a2ui-col" [style.align-items]="alignItems()" [style.gap.px]="gapPx()">
@for (key of childKeys(); track key) {
<render-element [elementKey]="key" [spec]="spec()" />
}
</div>
`,
styles: [`
.a2ui-col {
display: flex;
flex-direction: column;
}
`],
})
export class A2uiColumnComponent {
readonly childKeys = input<string[]>([]);
Expand All @@ -28,10 +38,7 @@ export class A2uiColumnComponent {
readonly emit = input<(event: string) => void>(() => { /* noop */ });
readonly loading = input<boolean>(false);

protected readonly colClass = computed(() => {
const alignMap: Record<ColumnAlignment, string> = {
start: 'items-start', center: 'items-center', end: 'items-end', stretch: 'items-stretch',
};
return `flex flex-col gap-${this.gap()} ${alignMap[this.alignment()]}`;
});
protected readonly alignItems = computed(() => ALIGN_MAP[this.alignment()] ?? 'flex-start');
/** Convert the Tailwind gap unit (multiples of 4px) to pixels. */
protected readonly gapPx = computed(() => this.gap() * 4);
}
27 changes: 21 additions & 6 deletions libs/chat/src/lib/a2ui/catalog/date-time-input.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,37 @@ import { emitBinding } from './emit-binding';
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-col gap-1">
<div class="a2ui-dti">
@if (label()) {
<label [htmlFor]="_inputId" class="text-xs" style="color: var(--a2ui-label, rgba(255,255,255,0.6));">{{ label() }}</label>
<label [htmlFor]="_inputId" class="a2ui-dti__label">{{ label() }}</label>
}
<input
[id]="_inputId"
[type]="htmlInputType()"
[value]="value()"
class="rounded-lg px-3 py-2 text-sm"
[style.background]="'var(--a2ui-input-bg, rgba(255,255,255,0.05))'"
[style.color]="'var(--a2ui-input-text, white)'"
[style.border]="'1px solid var(--a2ui-border, rgba(255,255,255,0.1))'"
class="a2ui-dti__input"
(change)="onChange($event)"
/>
</div>
`,
styles: [`
.a2ui-dti { display: flex; flex-direction: column; gap: 4px; }
.a2ui-dti__label {
font-size: 12px;
color: var(--a2ui-label, rgba(255,255,255,0.6));
}
.a2ui-dti__input {
padding: 8px 12px;
font-size: 14px;
border-radius: 8px;
background: var(--a2ui-input-bg, rgba(255,255,255,0.05));
color: var(--a2ui-input-text, white);
border: 1px solid var(--a2ui-border, rgba(255,255,255,0.1));
outline: none;
transition: border-color 120ms;
}
.a2ui-dti__input:focus { border-color: var(--a2ui-primary, #4f8df5); }
`],
})
export class A2uiDateTimeInputComponent {
private static _idCounter = 0;
Expand Down
20 changes: 18 additions & 2 deletions libs/chat/src/lib/a2ui/catalog/divider.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,27 @@ import type { Spec } from '@json-render/core';
standalone: true,
template: `
@if (orientation() === 'vertical') {
<div class="inline-block self-stretch border-l border-white/10 mx-2"></div>
<div class="a2ui-divider a2ui-divider--vertical"></div>
} @else {
<hr class="border-white/10 my-2" />
<hr class="a2ui-divider a2ui-divider--horizontal" />
}
`,
styles: [`
.a2ui-divider--horizontal {
display: block;
width: 100%;
border: none;
border-top: 1px solid var(--a2ui-border, rgba(255,255,255,0.1));
margin: 8px 0;
}
.a2ui-divider--vertical {
display: inline-block;
align-self: stretch;
width: 1px;
background: var(--a2ui-border, rgba(255,255,255,0.1));
margin: 0 8px;
}
`],
})
export class A2uiDividerComponent {
/** Canonical v1 spec name. The LLM emits this. */
Expand Down
10 changes: 9 additions & 1 deletion libs/chat/src/lib/a2ui/catalog/icon.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ import type { Spec } from '@json-render/core';
standalone: true,
template: `
<span
class="inline-flex items-center justify-center select-none"
class="a2ui-icon"
[style.font-size]="size() ? size() + 'px' : '1.125rem'"
>{{ icon() }}</span>
`,
styles: [`
.a2ui-icon {
display: inline-flex;
align-items: center;
justify-content: center;
user-select: none;
}
`],
})
export class A2uiIconComponent {
/** v1 prop name: icon (resolved string, e.g. a Unicode symbol or ligature name). */
Expand Down
9 changes: 8 additions & 1 deletion libs/chat/src/lib/a2ui/catalog/image.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import type { Spec } from '@json-render/core';
standalone: true,
template: `
<img
class="a2ui-img"
[src]="url()"
[alt]="alt()"
[style.width]="width() ? width() + 'px' : null"
[style.height]="height() ? height() + 'px' : null"
class="max-w-full rounded"
/>
`,
styles: [`
.a2ui-img {
display: block;
max-width: 100%;
border-radius: 4px;
}
`],
})
export class A2uiImageComponent {
readonly url = input<string>('');
Expand Down
19 changes: 17 additions & 2 deletions libs/chat/src/lib/a2ui/catalog/list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ import { RenderElementComponent } from '@ngaf/render';
}
</div>
`,
styles: [`
.a2ui-list--vertical {
display: flex;
flex-direction: column;
gap: 4px;
overflow-y: auto;
max-height: 384px;
}
.a2ui-list--horizontal {
display: flex;
flex-direction: row;
gap: 4px;
overflow-x: auto;
}
`],
})
export class A2uiListComponent {
readonly childKeys = input<string[]>([]);
Expand All @@ -26,7 +41,7 @@ export class A2uiListComponent {

protected readonly listClass = computed(() => {
return this.direction() === 'horizontal'
? 'flex flex-row gap-1 overflow-x-auto'
: 'flex flex-col gap-1 overflow-y-auto max-h-96';
? 'a2ui-list--horizontal'
: 'a2ui-list--vertical';
});
}
Loading
Loading