Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
42f3c87
feat(ai-cu): improve interactive infrastructure generation
chienyuanchang Oct 27, 2026
6ece9b2
fix(ai-cu): improve actionable and locatable diagnostics
chienyuanchang Oct 27, 2026
69e7fb7
fix(ai-cu): wrap analysis failure diagnostic for lint
chienyuanchang Oct 27, 2026
9469f0f
test(ai-cu): canonicalize analysis diagnostic output fixture
chienyuanchang Oct 27, 2026
a83678f
fix(ai-cu): scope prompt retries and locate selected schema aliases
chienyuanchang Oct 28, 2026
3cce198
feat(ai-cu): add opt-in terminal Markdown previews
chienyuanchang Oct 28, 2026
749ccca
Merge origin/main into ai-cu/terminal-markdown-rendering
chienyuanchang Nov 3, 2026
737e201
fix(ai-cu): clarify version output and separate update notices
chienyuanchang Nov 4, 2026
d58b9d3
refine test data
chienyuanchang Nov 4, 2026
73862d5
fix(ai-cu): highlight provisioning guidance and prompt questions
chienyuanchang Nov 4, 2026
6a20f26
fix(ai-cu): rename terminal preview flag to pretty
chienyuanchang Nov 4, 2026
7172ecc
docs(ai-cu): focus terminal preview guidance on user behavior
chienyuanchang Nov 4, 2026
36bc5f6
fix(ai-cu): clarify pretty-print help and color terminal tables
chienyuanchang Nov 4, 2026
5db056f
docs(ai-cu): add release notes for terminal pretty-printing
chienyuanchang Nov 4, 2026
226f84e
chore(ai-cu): bump extension version to 0.1.2-preview
chienyuanchang Nov 4, 2026
48c678e
fix(ai-cu): preserve reference links across HTML tables
chienyuanchang Nov 4, 2026
701dab3
fix(ai-cu): preserve document structure in pretty previews
chienyuanchang Nov 6, 2026
a0e165d
reserve the completed URL
chienyuanchang Nov 6, 2026
22a1a00
fix(ai-cu): show console analysis progress and correct endpoint guidance
chienyuanchang Nov 6, 2026
ae4a56a
fix(ai-cu): remove dimming from preview metadata and comments
chienyuanchang Nov 6, 2026
fa34888
feat(ai-cu): add pretty shorthand and simplify user documentation
chienyuanchang Nov 6, 2026
1c1dd44
fix(ai-cu): filter semicolonless encoded terminal controls
chienyuanchang Nov 6, 2026
3b2c1ca
fix(ai-cu): render empty cells and headerless preview tables
chienyuanchang Nov 7, 2026
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
16 changes: 15 additions & 1 deletion cli/azd/extensions/azure.ai.content-understanding/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# Release History

## 0.1.1-preview (Unreleased)
## 0.1.2-preview (Unreleased)

### Features Added

- Add `azd ai cu analyze --pretty` (`-p`) to pretty-print a single result in the
terminal, with formatted Markdown, colored headings, and simple HTML tables
displayed as tables or labeled cards.

### Other Changes

- Improve the readability of `azd ai cu version` text output.
- Highlight commands, paths, and identifiers in infrastructure generation
guidance, and use blue text for wizard questions. Respect `NO_COLOR`.

## 0.1.1-preview (2026-11-03)

### Features Added

Expand Down
8 changes: 8 additions & 0 deletions cli/azd/extensions/azure.ai.content-understanding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ Analysis displays YAML front matter and Markdown in the console by default.
azd ai cu analyze invoice.pdf --output-file invoice.result.md
```

By default, stdout contains Markdown and HTML tables as text, including when
redirected. Use `azd ai cu analyze invoice.pdf --pretty` (or `-p`) to pretty-print results
in the terminal, with formatted Markdown and simple HTML tables displayed as
tables or labeled cards. Colors distinguish headings and tables when enabled.
The flag only applies to one streamed LLM-input
result and cannot be combined with saved-output options or JSON. See
[pretty-print behavior and limitations](docs/usage.md#pretty-print-results-in-the-terminal).

Comment thread
chienyuanchang marked this conversation as resolved.
Resolved
For the complete JSON response, including service warnings and usage when
present, use `--output json`. Display it in the console or save it to a file:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ words:
- contentunderstanding
- noninteractive
overrides:
- filename: "internal/preview/{render,metadata}.go"
words:
- goldmark
- rowspan
- yuin
- filename: "internal/**/*.go"
words:
- exterrors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ for role definitions.

## Interactive user workflow

The generation wizard highlights commands, paths, and identifiers in its guidance,
and uses blue question text to distinguish prompts from explanations. Set
`NO_COLOR=1` for plain text. JSON and silent output modes do not use color.

Sign in first, then use two steps: **generate files**, followed by **deploy
resources**. Only the second step creates or changes Azure resources and can
incur charges. Have your subscription and intended region ready, and check
Expand Down
57 changes: 57 additions & 0 deletions cli/azd/extensions/azure.ai.content-understanding/docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ development. Start with the [README](https://github.com/Azure/azure-dev/blob/mai
For resource creation, permissions, model deployment, and service defaults, use
the separate [Microsoft Foundry provisioning guide](https://github.com/Azure/azure-dev/blob/main/cli/azd/extensions/azure.ai.content-understanding/docs/provisioning.md).

## Check the installed extension version

Run `azd ai cu version` to display the installed extension version and name.
For scripts, use `azd ai cu version --output json`, which returns
the extension ID as `name` and the installed version as `version`.

## Connect to Microsoft Foundry and check setup

You need a Microsoft Foundry resource endpoint and an identity with access to
Expand Down Expand Up @@ -147,6 +153,50 @@ azd ai cu analyze \
--output-file invoice.result.md
```

### Pretty-print results in the terminal

By default, stdout contains Markdown and HTML tables as text. Redirect or pipe
this output to save it or render it with another tool:

```bash
azd ai cu analyze invoice.pdf > invoice.result.md
```

Pretty-print results in the terminal with `--pretty` (or `-p`); omit the flag or
use `--pretty=false` for the default text output:

```bash
azd ai cu analyze invoice.pdf --pretty
```

Pretty-printing formats Markdown for terminal reading. The preview includes
document metadata with `---` delimiters and page comments, and keeps source line
breaks. Reference-style links work across tables.

HTML tables display all columns and switch to labeled cards when they are too wide
for the terminal.

When color is enabled, metadata, page comments, and generated row labels are gray.
Headings share a consistent color, tables use a distinct color, and layout notices
are yellow.

Keep these limitations in mind:

- Unsupported tables, such as those with merged cells or complex headers, display
an explanatory notice followed by their source HTML.
- Long values can overflow, and some Unicode text or emoji may not align correctly.
Right-to-left text display depends on your terminal. Markdown tables may shorten
long links.
- Preview text removes terminal controls. Links and images are not fetched.
- Piped or redirected previews are uncolored and use an 80-column layout.

`--pretty` requires a single streamed LLM-input result. It cannot be combined with
`--output json`, `--output none`, `--llm-input=false`, `--usage`, `--dry-run`,
`--output-file`, or `--output-dir`. The flag affects only the streamed preview,
not saved result files or reports. To save Markdown/HTML, omit `--pretty`.

### Complete JSON response

For the complete JSON response envelope, including the result, service warnings,
and usage when present, use `--output json`. These examples display the JSON
in the console and save it to a file, respectively:
Expand Down Expand Up @@ -210,6 +260,13 @@ Missing-resource errors suggest checking the endpoint and available analyzers;
creation conflicts suggest inspecting the existing analyzer and choosing an unused ID.
Service response bodies and authored content remain hidden.

In an interactive terminal, analysis shows an animated `Analyzing <target>...`
status in the action color (`NO_COLOR` disables color). Concurrent inputs share
one status line that cycles through active targets. Local targets show the file
name; URL targets omit credentials, query parameters, and fragments. The status
clears before results or errors appear and is omitted for redirected or piped
stdout, JSON/none output, and dry runs. It is never saved in result or report files.

- Batch concurrency defaults to 4; `--concurrency` accepts 1-32.
- More than 50 selected inputs require confirmation or explicit `--yes`.
- Existing results fail by default. Use `--on-existing skip` to keep them or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ tags:
- ai
- content-understanding
usage: azd ai cu <command> [options]
version: 0.1.1-preview
version: 0.1.2-preview
10 changes: 5 additions & 5 deletions cli/azd/extensions/azure.ai.content-understanding/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ require (
github.com/buger/jsonparser v1.6.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/glamour v1.0.0 // indirect
github.com/charmbracelet/glamour v1.0.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
github.com/charmbracelet/x/ansi v0.11.9 // indirect
github.com/charmbracelet/x/ansi v0.11.9
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/exp/slice v0.1.0 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
Expand Down Expand Up @@ -86,7 +86,7 @@ require (
github.com/theckman/yacspin v0.13.12 // indirect
github.com/xo/terminfo v1.2.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/yuin/goldmark v1.8.6 // indirect
github.com/yuin/goldmark v1.8.6
github.com/yuin/goldmark-emoji v1.0.6 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.47.0 // indirect
Expand All @@ -97,10 +97,10 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.58.0 // indirect
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e // indirect
golang.org/x/net v0.61.0 // indirect
golang.org/x/net v0.61.0
golang.org/x/sync v0.24.0 // indirect
golang.org/x/sys v0.49.0
golang.org/x/term v0.47.0 // indirect
golang.org/x/term v0.47.0
golang.org/x/text v0.43.0 // indirect
golang.org/x/time v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941 // indirect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type RunOptions struct {
Mode cuclient.AnalysisMode
Concurrency int
Usage bool
// Progress can be called concurrently; implementations must serialize their output.
Progress func(index int, input Input, active bool) error
}

type Record struct {
Expand Down Expand Up @@ -205,7 +207,7 @@ func analyzeInputs(
return
}
index := jobs[position]
payload, usage, err := analyzeOne(ctx, client, inputs[index], llm, options)
payload, usage, err := analyzeWithProgress(ctx, client, inputs[index], index, llm, options)
// Drain all started jobs before closing the service session, including on cancellation.
completed <- outcome{index: index, payload: payload, usage: usage, err: err}
}
Expand All @@ -218,6 +220,21 @@ func analyzeInputs(
return completed
}

func analyzeWithProgress(
ctx context.Context, client cuclient.Client, input Input, index int, llm bool, options RunOptions,
) (any, json.RawMessage, error) {
if options.Progress != nil {
if err := options.Progress(index, input, true); err != nil {
return nil, nil, err
}
}
payload, usage, err := analyzeOne(ctx, client, input, llm, options)
if options.Progress != nil {
err = errors.Join(err, options.Progress(index, input, false))
}
return payload, usage, err
}

func analyzeOne(
ctx context.Context, client cuclient.Client, input Input, llm bool, options RunOptions,
) (any, json.RawMessage, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,91 @@ package analysis
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"sync"
"testing"

"azure.ai.content-understanding/internal/cuclient"
"azure.ai.content-understanding/internal/exterrors"
"azure.ai.content-understanding/internal/testutil"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestExecutionProgressTracksOnlyStartedInputs(t *testing.T) {
inputs := discoverFiles(t, "a.pdf", "b.pdf", "skipped.pdf")
plan, err := PlanOutputs(inputs, OutputOptions{Existing: "error"})
require.NoError(t, err)
plan.Outputs[2].Skipped = true
var mu sync.Mutex
events := map[int][]bool{}
options := runOptions()
options.Progress = func(index int, input Input, active bool) error {
mu.Lock()
defer mu.Unlock()
assert.Equal(t, inputs[index], input)
events[index] = append(events[index], active)
return nil
}
client := fakeAnalysisClient{analyze: func(
context.Context, cuclient.AnalyzeRequest,
) (cuclient.AnalyzeResponse, error) {
return cuclient.AnalyzeResponse{JSON: json.RawMessage(terminal)}, nil
}}
_, err = Execute(t.Context(), client, plan, options)
require.NoError(t, err)
require.Equal(t, map[int][]bool{0: {true, false}, 1: {true, false}}, events)
}

func TestAnalysisProgressCompletesOnFailureAndCancellation(t *testing.T) {
for _, cancelled := range []bool{false, true} {
t.Run(fmt.Sprint(cancelled), func(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
input := discoverFiles(t, "invoice.pdf")[0]
var events []bool
options := runOptions()
options.Progress = func(_ int, _ Input, active bool) error {
events = append(events, active)
return nil
}
want := errors.New("analysis failed")
if cancelled {
want = context.Canceled
}
client := fakeAnalysisClient{analyze: func(
context.Context, cuclient.AnalyzeRequest,
) (cuclient.AnalyzeResponse, error) {
if cancelled {
cancel()
}
return cuclient.AnalyzeResponse{}, want
}}
_, _, err := analyzeWithProgress(ctx, client, input, 0, false, options)
require.ErrorIs(t, err, want)
require.Equal(t, []bool{true, false}, events)
})
}
}

func TestAnalysisProgressFailurePreventsRequest(t *testing.T) {
want := errors.New("progress writer failed")
options := runOptions()
options.Progress = func(int, Input, bool) error { return want }
client := fakeAnalysisClient{analyze: func(
context.Context, cuclient.AnalyzeRequest,
) (cuclient.AnalyzeResponse, error) {
t.Error("must not analyze after progress output fails")
return cuclient.AnalyzeResponse{}, nil
}}
_, _, err := analyzeWithProgress(t.Context(), client, Input{}, 0, false, options)
require.ErrorIs(t, err, want)
}

func TestExecutionFailureDiagnosticsAreActionableAndBounded(t *testing.T) {
for _, count := range []int{1, 12} {
t.Run(fmt.Sprint(count), func(t *testing.T) {
Expand Down
Loading
Loading