# Intelligent UI [![npm](https://img.shields.io/npm/v/@comeonzhj/dsh-intelligent-ui?label=npm)](https://www.npmjs.com/package/@comeonzhj/dsh-intelligent-ui) [![CI](https://github.com/comeonzhj/dsh-intelligent-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/comeonzhj/dsh-intelligent-ui/actions/workflows/ci.yml) [![license](https://img.shields.io/npm/l/@comeonzhj/dsh-intelligent-ui)](LICENSE) [![tests](https://img.shields.io/badge/tests-91-brightgreen)](test) **Text + rich card answers for DeepSeek Harness.** The model writes the interface in **DIL** — Markdown for prose, JSX-like tags for components, JavaScript for state and logic. The host compiles that into a JavaScript **program** plus a **constants** table. A sandboxed client runtime executes the program and emits **operations**. The page applies those operations to its own component tree, styled with the harness design system. Moving a slider, ticking a switch or picking an option happens **locally in the browser, with no model call**. Only an explicit submit sends the answer back, as the user's next message. This is a reverse-engineered implementation of ChatGPT's Intelligent UI. ![A delivered card](docs/card.png) --- ## Contents - [What it is](#what-it-is) - [Install](#install) - [Two tools](#two-tools) - [DIL in brief](#dil-in-brief) - [Components and interactions](#components-and-interactions) - [How an answer gets back](#how-an-answer-gets-back) - [Where the card lives](#where-the-card-lives) - [Settings](#settings) - [Development and tests](#development-and-tests) - [Layout](#layout) - [Known limits](#known-limits) - [Contributing](#contributing) - [Licence](#licence) --- ## What it is The reference implementation divides the work between the model, a backend server, the client and a design system. Every one of those layers has a home on DSH: | Layer in ChatGPT's Intelligent UI | Here | Where | |---|---|---| | Inference format: Markdown + JSX-like tags + JS | DIL, in the `dil` argument of a tool | `lib/compile.js`, `index.js` | | Server-side compilation to a JS program + JSON document | The host compiles at call time and persists the "raw text / program / constants" triple as the tool result's `presentationMeta` | `index.js` | | Repair and validation against a catalog | Unknown properties and bad literals are dropped at compile time and recorded as diagnostics | `lib/compile.js`, `lib/catalog.js` | | Partial-response compilation while streaming | Every partial argument prefix is recompiled; an unfinished statement is dropped and open elements are auto-closed | `lib/compile.js`, `src/client/app.js` | | Sandboxed runtime (hidden iframe → Worker → lockdown) | A Blob Worker whose global scope loses network, timers, messaging and dynamic evaluation before the first program; plus iframe and in-page fallbacks | `src/runtime/runtime.js` | | Reconciler producing UI operations | A small keyed reconciler: absolute-keyed hook slots, tree diff, integer-encoded operation stream with a string table | `src/runtime/runtime.js` | | Rendering against native components | Drawn from the catalog, with every colour, gap and radius coming from `--dsw-*` tokens | `src/client/components.js` | | Design system and catalog | 49 components, 4 composite components, 5 token scales | `lib/catalog.js` | | Composite components written in DIL, shipped prebuilt | `statRow`, `pricingCard`, `productCard`, `choiceCard` | `lib/composite.js` | | AppBlock escape hatch | **Not implemented.** On DSH its equivalent is a whole client plugin, not an inline block. | — | Two deliberate decisions: - **`{expr}` is the interpolation; a `$` before it is ordinary text**, so `${price}` renders as `$29`. This is not an accident of the parser: in the reverse-engineered constants table the source `${price}/mo` corresponds to `"4": "$"` and `"5": "/mo"`, which is only possible if `$` is literal. The prompt states the rule, so the model can use it deliberately for currency. - **Property expressions are not wrapped individually.** An element whose properties are not all literals is wrapped once, so a property that throws removes that one element. The reference capture shows the same thing (`value: seats` bare, inside a wrapped `__dil.jsx` call). --- ## Install This is a **bundle**: one npm package carrying both a host half (`index.js`) and a browser half (`client.js`). ```sh # from npm dsh plugin --profile add @comeonzhj/dsh-intelligent-ui # from GitHub (pin a revision with #) dsh plugin --profile add github:comeonzhj/dsh-intelligent-ui ``` Then **restart that profile** — bundle membership is fixed at start. Afterwards both tools are available to every agent in it. > **Why the package is scoped**: `dsh-intelligent-ui` on npm is already a > generative-UI plugin for DSH by another author. A name differing only by a > suffix would be needlessly confusing, so this one is published under the > author scope while keeping the concept's name. ### Just to try it, without installing anywhere ```sh node scripts/dev-profile.mjs --boot ``` That writes a `--patch` overlay (mounting this checkout by absolute path), creates a profile named `intelligent-ui-dev` if needed, boots it, and prints the URL with its auth token. Your own profile is never touched. > **If `dsh plugin add` hangs**, the `pnpm` on your `PATH` is the cause (some > shims recurse through `pnpm dlx dlx dlx …`). Use the one the app ships: > `/Applications/DeepSeek Harness.app/Contents/Resources/runtime/pnpm/bin/pnpm.mjs`, > wrapped on your `PATH`. ## Two tools A card can be two very different things, so there are two tools rather than one with a flag: | Tool | Purpose | Feedback entry point | |---|---|---| | `render_ui` | **Deliver** a card. The card is the answer; stop after calling it. | **None** | | `ask_with_ui` | **Ask the human** mid-task, with the options and controls in the card. `ask` is required. | Yes (submit button + optional note box) | Why separate: **a finished deliverable should not carry a feedback entry point.** The conversation already has an input box, and the native one is better — it carries attachments, slash commands and prompt history. A "was this helpful?" form bolted onto the end only makes a finished answer look unfinished. This is not just prompt guidance: `render_ui` has no `ask` in its parameter schema at all, and the host drops one if a model sends it anyway, recording an `ask_not_allowed` diagnostic. ![A clarification card](docs/ask-card.png) The answer footer appears only under `ask_with_ui`, and a card has **exactly one way to be submitted** — either a `