wip: agui integration

This commit is contained in:
2026-02-25 19:51:58 +01:00
parent 5efbcfe03a
commit fcdf869a7c
59 changed files with 3467 additions and 267 deletions

View File

@@ -0,0 +1,195 @@
import { describe, expect, it } from 'vitest';
import { extractAssistantPanelSpec, extractAssistantResponseContent } from '../../../src/renderer/navigation/assistantPanelSpec';
describe('assistantPanelSpec', () => {
it('extracts valid spec from fenced json block', () => {
const raw = [
'Here is the analysis summary.',
'```json',
'{"specVersion":"1","elements":[{"type":"metric","label":"Drafts","value":"12"}]}',
'```',
].join('\n');
const result = extractAssistantPanelSpec(raw);
expect(result).not.toBeNull();
expect(result?.specVersion).toBe('1');
expect(result?.elements).toHaveLength(1);
expect(result?.elements[0]).toEqual({ type: 'metric', label: 'Drafts', value: '12' });
});
it('returns null for invalid schema payload', () => {
const raw = '{"specVersion":"1","elements":[{"type":"table","columns":[]}]}';
const result = extractAssistantPanelSpec(raw);
expect(result).toBeNull();
});
it('ignores yaml payloads to keep the protocol JSON-only', () => {
const raw = [
'Here is your chart.',
'```yaml',
'specVersion: "1"',
'elements:',
' - type: chart',
' chartType: bar',
' title: Posts by Month',
' series:',
' - label: Jan',
' value: 10',
' - label: Feb',
' value: 20',
'```',
].join('\n');
const result = extractAssistantPanelSpec(raw);
expect(result).toBeNull();
});
it('extracts text plus ui payload from mixed assistant response', () => {
const raw = [
'I found two weak months. Please confirm how to proceed.',
'```json',
'{"specVersion":"1","elements":[{"type":"chart","chartType":"bar","title":"Posts by Month","series":[{"label":"Jan","value":10},{"label":"Feb","value":20}]}]}',
'```',
].join('\n\n');
const result = extractAssistantResponseContent(raw);
expect(result.displayText).toContain('I found two weak months');
expect(result.panelSpec).not.toBeNull();
expect(result.panelSpec?.elements[0]).toMatchObject({ type: 'chart', chartType: 'bar' });
});
it('normalizes tab-channel envelope payloads into canonical panel spec', () => {
const raw = JSON.stringify({
type: 'tab',
title: 'Posts mit Tag spielen',
id: 'spielen-tag-analysis',
content: {
type: 'tabs',
tabs: [
{
id: 'yearly-chart',
title: 'Jahresübersicht',
content: {
type: 'chart',
chartType: 'bar',
data: {
labels: ['2011', '2013'],
datasets: [{ data: [2, 8] }],
},
},
},
],
},
});
const result = extractAssistantPanelSpec(raw);
expect(result).not.toBeNull();
expect(result?.specVersion).toBe('1');
expect(result?.elements[0]).toMatchObject({ type: 'tabs' });
});
it('normalizes chartjs-like chart payloads to series format', () => {
const raw = JSON.stringify({
specVersion: '1',
elements: [
{
type: 'chart',
chartType: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar'],
datasets: [{ data: [23, 10, 14] }],
},
},
],
});
const result = extractAssistantPanelSpec(raw);
expect(result).not.toBeNull();
expect(result?.elements[0]).toMatchObject({
type: 'chart',
chartType: 'bar',
series: [
{ label: 'Jan', value: 23 },
{ label: 'Feb', value: 10 },
{ label: 'Mar', value: 14 },
],
});
});
it('parses extended widgets including chart, form, datePicker, card, image, input and tabs', () => {
const raw = JSON.stringify({
specVersion: '1',
elements: [
{
type: 'chart',
chartType: 'bar',
title: 'Posts by Month',
series: [
{ label: 'Jan', value: 10 },
{ label: 'Feb', value: 20 },
],
},
{
type: 'input',
key: 'query',
label: 'Search Query',
inputType: 'text',
placeholder: 'Find post',
},
{
type: 'datePicker',
key: 'publishDate',
label: 'Publish Date',
},
{
type: 'form',
formId: 'meta-form',
title: 'Update Metadata',
submitLabel: 'Apply',
action: 'updatePostMetadata',
fields: [
{ key: 'title', label: 'Title', inputType: 'text' },
{ key: 'isDraft', label: 'Draft', inputType: 'checkbox' },
],
},
{
type: 'card',
title: 'Suggestion',
body: 'Consider adding tags.',
actions: [
{ label: 'Open Tags', action: 'switchView', payload: { view: 'tags' } },
],
},
{
type: 'image',
src: 'https://example.com/image.png',
alt: 'Preview',
caption: 'Generated preview',
},
{
type: 'tabs',
tabs: [
{
id: 'summary',
label: 'Summary',
elements: [{ type: 'text', text: 'Summary text' }],
},
{
id: 'details',
label: 'Details',
elements: [{ type: 'metric', label: 'Count', value: '42' }],
},
],
},
],
});
const result = extractAssistantPanelSpec(raw);
expect(result).not.toBeNull();
expect(result?.elements).toHaveLength(7);
});
});