diff --git a/.claude/launch.json b/.claude/launch.json index 606ffb2..d83cc83 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -15,6 +15,15 @@ "cd playground && flutter run -d web-server --web-port=8123" ], "port": 8123 + }, + { + "name": "stacflow-example", + "runtimeExecutable": "sh", + "runtimeArgs": [ + "-c", + "cd packages/stacflow/example && flutter run -d web-server --web-port=8124" + ], + "port": 8124 } ] } diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fede0cc..d45c429 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -18,11 +18,11 @@ ## Checklist -- [ ] `flutter analyze lib` and `flutter analyze` in `example/` and `playground/` are clean -- [ ] `dart format .` applied +- [ ] `dart run melos run analyze` is clean +- [ ] `dart run melos run format` applied - [ ] Exercised in the playground — with a stage demo added or updated if this is a new component or variant -- [ ] Any new entry under `dependencies:` in `pubspec.yaml` is flutter.dev-published, forces no configuration on hosts that never use the feature, and is argued in this PR +- [ ] Any new entry under `dependencies:` in `packages/flow_ui/pubspec.yaml` is flutter.dev-published, forces no configuration on hosts that never use the feature, and is argued in this PR - [ ] Nothing model-facing — no prompts, schemas, or provider/network calls -- [ ] New public API is exported from `lib/flow_ui.dart` and documented in `docs/` and the README table -- [ ] `CHANGELOG.md` updated for user-facing changes, with breaking changes called out +- [ ] New public API is exported from `packages/flow_ui/lib/flow_ui.dart` and documented in `docs/` and the README table +- [ ] `packages/flow_ui/CHANGELOG.md` updated for user-facing changes, with breaking changes called out - [ ] PR title follows conventional commits (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82f33c7..b2648eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,6 @@ on: pull_request: workflow_dispatch: -# A new push to the same ref supersedes the run already in flight. concurrency: group: ci-${{ github.ref }} cancel-in-progress: true @@ -15,8 +14,8 @@ permissions: contents: read jobs: - package: - name: Package + workspace: + name: Workspace runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -28,60 +27,73 @@ jobs: - run: flutter pub get + - name: Example key file + run: cp packages/stacflow/example/lib/env.example.dart packages/stacflow/example/lib/env.dart + - name: Format run: dart format --output=none --set-exit-if-changed . - # Scoped to lib/: from the repo root the analyzer also walks into - # example/ and playground/, which resolve their own dependencies and - # are analysed in the `apps` job instead. - name: Analyze - run: flutter analyze lib + run: dart run melos run analyze - # test/ is deliberately empty while the component surface is still being - # reshaped design-first; this runs the day the first test lands. - name: Test - run: | - if find test -name '*_test.dart' 2>/dev/null | grep -q .; then - flutter test - else - echo "No tests yet — skipping." - fi - - # Catches packaging mistakes (a stale .pubignore, a missing asset, a - # broken README link) before a release tag, not after. + run: dart run melos run test + + package: + name: flow_ui package + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/flow_ui + steps: + - uses: actions/checkout@v7 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - run: flutter pub get + + - run: flutter analyze + - name: Publish dry run run: flutter pub publish --dry-run apps: - name: ${{ matrix.app }} + name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - app: [example, playground] + include: + - name: example + dir: packages/flow_ui/example + - name: stacflow example + dir: packages/stacflow/example + - name: playground + dir: playground steps: - uses: actions/checkout@v7 - # The example reads its Gemini key from lib/env.g.dart, which is - # gitignored so a real key can never land in a commit — meaning a - # fresh checkout doesn't have it. Stub it with an empty key, the - # same state a fresh clone runs with (see example/README.md). - - name: Stub the example's key file - if: matrix.app == 'example' - run: | - printf '// CI stub — the real file is gitignored.\nconst String apiKey = %s;\n' "''" \ - > example/lib/env.g.dart - - uses: subosito/flutter-action@v2 with: channel: stable cache: true + - name: Example key file + if: matrix.dir == 'packages/stacflow/example' + run: cp lib/env.example.dart lib/env.dart + working-directory: ${{ matrix.dir }} + - run: flutter pub get - working-directory: ${{ matrix.app }} + working-directory: ${{ matrix.dir }} - run: flutter analyze - working-directory: ${{ matrix.app }} + working-directory: ${{ matrix.dir }} + + - run: flutter build web --release + working-directory: ${{ matrix.dir }} docs: name: Docs site @@ -89,8 +101,6 @@ jobs: steps: - uses: actions/checkout@v7 - # `build:site` compiles the playground to wasm before Astro builds, so - # this job needs both toolchains. - uses: subosito/flutter-action@v2 with: channel: stable @@ -105,7 +115,41 @@ jobs: - run: npm ci working-directory: docs - # The same build the deploy runs: playground → public/playground, then - # the site. Keeps a broken playground from reaching flowui.stac.dev. - run: npm run build:site working-directory: docs + + # Dependency-direction rules the analyzer cannot express. Greps anchor to + # import/export directives so prose in doc comments never trips them. + boundaries: + name: Boundaries + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: flow_ui never depends on the SDK package + run: | + ! grep -rnE "^\s*(import|export)\s+'package:stacflow/" packages/flow_ui/lib \ + || { echo "::error::flow_ui is the presentation layer; the SDK depends on it, never the reverse"; exit 1; } + ! grep -nE "^\s*stacflow:" packages/flow_ui/pubspec.yaml \ + || { echo "::error::flow_ui must not depend on the SDK package"; exit 1; } + + contracts: + name: Contracts + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - run: flutter pub get + + - name: Generated Dart matches contracts/ + run: | + dart run tool/contracts_gen.dart + dart format packages/stacflow/lib/src/generated >/dev/null + git add -N packages/stacflow/lib/src/generated + git diff --exit-code -- packages/stacflow/lib/src/generated \ + || { echo "::error::generated types are stale: run dart run tool/contracts_gen.dart && dart format packages/stacflow/lib/src/generated"; exit 1; } diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index da0b86f..ac32566 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,33 +1,62 @@ -# One release trigger, everything ships: pushing a version tag publishes -# the package to pub.dev AND deploys the docs site — playground included, -# it's built into dist/playground/ — to Cloudflare Pages. +# One tag per package release: # -# git tag v0.2.0 && git push origin v0.2.0 +# git tag flow_ui-v0.4.0 && git push origin flow_ui-v0.4.0 # -# Package authentication is OIDC — no token lives in this repo. It requires -# automated publishing to be enabled once on pub.dev (flow_ui → Admin → -# Automated publishing) for StacDev/flow_ui with the tag pattern -# `v{{version}}`. +# publishes packages/flow_ui to pub.dev and deploys the docs site (playground +# included) to Cloudflare Pages. Publishing is OIDC: pub.dev → flow_ui → Admin +# → Automated publishing must list StacDev/flow_ui with the tag pattern +# `flow_ui-v{{version}}`. A future package gets its own tag glob below, a +# `case` line in `meta`, and its own pub.dev tag pattern. # -# The site deploy needs one-time repo configuration: -# secrets: CLOUDFLARE_API_TOKEN (Pages:Edit permission) -# CLOUDFLARE_ACCOUNT_ID -# variable: CLOUDFLARE_PAGES_PROJECT (the Pages project serving -# flowui.stac.dev) +# Site deploy configuration: secrets CLOUDFLARE_API_TOKEN (Pages:Edit) and +# CLOUDFLARE_ACCOUNT_ID; variable CLOUDFLARE_PAGES_PROJECT. name: Publish on: push: - tags: ['v[0-9]+.[0-9]+.[0-9]+*'] - # Redeploy the site without cutting a release, e.g. after a docs fix. + tags: + - 'flow_ui-v[0-9]+.[0-9]+.[0-9]+*' + - 'stacflow-v[0-9]+.[0-9]+.[0-9]+*' + # Redeploy the site without cutting a release. workflow_dispatch: jobs: - # The quality gate: the release only ships with full pub.dev points. - # Comparing granted against pana's own max (160 today) keeps the gate - # honest if pub.dev ever raises the ceiling. + meta: + name: Resolve package from tag + runs-on: ubuntu-latest + outputs: + package: ${{ steps.tag.outputs.package }} + dir: ${{ steps.tag.outputs.dir }} + version: ${{ steps.tag.outputs.version }} + steps: + - uses: actions/checkout@v7 + + - id: tag + shell: bash + run: | + if [ "$GITHUB_EVENT_NAME" != push ]; then + printf 'package=flow_ui\ndir=packages/flow_ui\nversion=\n' >> "$GITHUB_OUTPUT" + exit 0 + fi + tag="$GITHUB_REF_NAME" + package="${tag%-v*}" + version="${tag#"$package-v"}" + case "$package" in + flow_ui) dir=packages/flow_ui ;; + stacflow) dir=packages/stacflow ;; + *) echo "::error::no package for tag $tag"; exit 1 ;; + esac + pubspec_version=$(sed -n 's/^version:[[:space:]]*//p' "$dir/pubspec.yaml" | tr -d '[:space:]') + if [ "$version" != "$pubspec_version" ]; then + echo "::error::tag says $version, $dir/pubspec.yaml says $pubspec_version" + exit 1 + fi + printf 'package=%s\ndir=%s\nversion=%s\n' "$package" "$dir" "$version" >> "$GITHUB_OUTPUT" + + # The release only ships with full pub.dev points. pana: name: Pana score gate + needs: meta runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -42,11 +71,11 @@ jobs: - name: Require a perfect score run: | dart pub global activate pana - dart pub global run pana --no-warning --json . > pana.json + dart pub global run pana --no-warning --json "${{ needs.meta.outputs.dir }}" > pana.json granted=$(jq -r '.scores.grantedPoints' pana.json) max=$(jq -r '.scores.maxPoints' pana.json) echo "pana: $granted / $max" - if [ "$granted" != "$max" ]; then + if [ "$granted" -lt "$max" ]; then echo "Points were dropped in these sections:" jq -r '.report.sections[] | select(.grantedPoints < .maxPoints) @@ -56,22 +85,21 @@ jobs: fi package: - # Manual dispatches redeploy the site only — pub.dev is tags-only. if: github.event_name == 'push' - needs: pana + needs: [meta, pana] permissions: - id-token: write # Required to mint the OIDC token pub.dev verifies. + id-token: write uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 + with: + working-directory: ${{ needs.meta.outputs.dir }} site: name: Docs + playground - needs: pana + needs: [meta, pana] runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - # `build:site` compiles the playground to wasm before Astro builds, - # so this job needs both toolchains — the same build CI validates. - uses: subosito/flutter-action@v2 with: channel: stable diff --git a/.gitignore b/.gitignore index 1e0c933..bff98da 100644 --- a/.gitignore +++ b/.gitignore @@ -16,17 +16,13 @@ migrate_working_dir/ *.iws .idea/ .env +.codex/ -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. -/pubspec.lock +# Flutter/Dart/Pub related. The root pubspec.lock is committed: the +# workspace holds apps. **/doc/api/ .dart_tool/ .flutter-plugins-dependencies -/build/ -/coverage/ +build/ +coverage/ +pubspec_overrides.yaml diff --git a/.pubignore b/.pubignore deleted file mode 100644 index 385b939..0000000 --- a/.pubignore +++ /dev/null @@ -1,39 +0,0 @@ -# .pubignore replaces .gitignore for publishing, so it repeats the relevant -# rules and adds the repo-only files the package archive should not carry. - -# From .gitignore -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ -*.iml -*.ipr -*.iws -.idea/ -.env -# Unrooted (unlike .gitignore) so example/ stays covered even when -# publishing from outside a git checkout, where .gitignore files are not -# consulted. -pubspec.lock -**/doc/api/ -.dart_tool/ -.flutter-plugins-dependencies -build/ -/coverage/ - -# Repo-only: the playground app, the documentation site, README screenshots -# (pub.dev loads them from the repository), and development/agent docs -playground/ -docs/ -doc/ -AGENTS.md -CLAUDE.md -.claude/ -.vscode/ -.metadata diff --git a/.vscode/launch.json b/.vscode/launch.json index 8edc41a..c612b35 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,13 @@ }, { "name": "example", - "cwd": "example", + "cwd": "packages/flow_ui/example", + "request": "launch", + "type": "dart" + }, + { + "name": "stacflow example", + "cwd": "packages/stacflow/example", "request": "launch", "type": "dart" } diff --git a/AGENTS.md b/AGENTS.md index a2dc0e8..d0f7af3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t ## What this is -**flow_ui** is a chat/assistant UI component library for Flutter — the presentation layer for AI assistant interfaces. It is a plain Flutter package (no codegen, no melos). Downstream packages depend on the public API exported from `lib/flow_ui.dart`, so treat it as a compatibility surface. +**flow_ui** is a chat/assistant UI component library for Flutter — the presentation layer for AI assistant interfaces. It is a plain Flutter package (no codegen) at `packages/flow_ui/`, inside a pub workspace driven by melos. Downstream packages depend on the public API exported from `lib/flow_ui.dart`, so treat it as a compatibility surface. Two hard constraints shape everything here: @@ -15,19 +15,24 @@ The theme, the conversation components (message, thread, streaming text, actions ## Layout -- Package at the repo root: `lib/`, `test/`, `pubspec.yaml`. -- `playground/` — the Flow UI Playground: a full Flutter app depending on the package via `path: ../`. Use it to demo and manually exercise components (every component has a stage demo, with variant pills and code snippets). +- Root `pubspec.yaml` is the pub workspace (members under `workspace:`) with the melos scripts; root `analysis_options.yaml` (very_good_analysis) governs `tool/` and the SDK package only. +- `packages/flow_ui/` — the published package: `lib/`, `example/` (the README's chat screen against Gemini), `assets/`, its own flutter_lints `analysis_options.yaml` and `.pubignore`. +- `packages/stacflow/` — the StacFlow SDK package (see "SDK package"), with `example/` (the README's chat screen against Gemini; flutter_lints like the flow_ui example). +- `playground/` — the Flow UI Playground: a full Flutter app and workspace member depending on `flow_ui: ^0.4.0`. Use it to demo and manually exercise components (every component has a stage demo, with variant pills and code snippets). +- `docs/` — the Astro site behind flowui.stac.dev. `contracts/` and `tool/` — the SDK wire contract and its codegen. ## Commands +**Don't write comments unless asked.** No doc comments, file headers or inline explanations in new or edited code; the code and the commit message carry the intent. The one exception is a comment a lint requires (for example `document_ignores` above an `// ignore`), kept to one line. Public API dartdoc is written only when the user asks for it. + From the repo root: ```bash -flutter test # all tests -flutter test test/some_widget_test.dart # single file -flutter test --plain-name "some test name" # single test by name -flutter analyze -dart format . +flutter pub get # resolves the whole workspace +dart run melos run analyze # dart analyze --fatal-infos in every member +dart run melos run format +dart run melos run test # SDK package tests; flow_ui has none yet +cd packages/flow_ui && flutter analyze && flutter pub publish --dry-run ``` Playground app: @@ -38,6 +43,29 @@ flutter pub get flutter run -d chrome # or any device ``` +## SDK package + +`packages/stacflow` is the StacFlow SDK: `StacFlowChat` (the controller) and `StacFlowChatView` on flow_ui, wired to Gemini, OpenAI and Claude with the developer's own key. One entrypoint, `package:stacflow/stacflow.dart`, which also re-exports flow_ui. Layout: `src/chat` (controller, state, view, and the flow_ui-to-wire reduction in `wire_history.dart`), `src/transport` (the `TurnTransport` seam, `TurnRequest` and the wire types, ids, the SSE parser; pure Dart), `src/providers` (the interface, the shared HTTP runner, one adapter per provider; pure Dart), `src/tools` (`Tool`, the call records and `runToolLoop`, the client-side tool loop shared by the controller and the smoke script; pure Dart). Rules: + +- `stacflow` depends on `flow_ui`, never the reverse (CI grep). +- Adapters emit the generated `SseEvent` union: `start` first, one `done` last, `seq` from 0. The runner in `turn_runner.dart` owns HTTP, abort, timeouts, key scrubbing and the tool-call bookkeeping (ids, argument buffering, the upgrade of `complete()` to `done{awaiting_client_tools}`); an adapter only declares `TurnRequest.tools`, maps frames, encodes the wire tool parts in history and replays its own raw content within a turn where the provider requires it (Gemini signatures and ids, Claude thinking blocks). +- The loop in `tool_loop.dart` owns dispatch, approval, timeouts, abort and the continuation segments; the controller only renders blocks into parts, keeps `ChatState.toolCalls`, and answers confirmations. `contracts/` is unchanged by tools: provider call ids ride inside the `tc_` ids. +- The API key is a private field set on exactly one header, and never appears in URLs, logs, `toString` or error text. +- No tests for now. Verify with `dart analyze --fatal-infos`, the smoke script (`cd packages/stacflow && dart run --define=PROVIDER=gemini --define=GEMINI_API_KEY=... tool/smoke.dart`, also `anthropic` and `openai`; `--define=ABORT_AFTER_FIRST_DELTA=true` and `--define=IMAGE=path.png` exercise abort and image input; `--define=TOOLS=true` registers a `get_time` tool and runs the loop, with `TOOL_PERMISSION=destructive` and `DECLINE=true` for the approval paths), and the example app (`cd packages/stacflow/example && flutter run` with the key in `lib/env.dart`, copied from `lib/env.example.dart` and gitignored; the `stacflow-example` entry in `.claude/launch.json` serves it on port 8124). Keep the example the runnable form of the README quickstart, against Gemini only, with the `set_theme` tool as its one tool. + +`contracts/` is the wire contract (SSE events, error codes); CI regenerates the Dart from it and fails on drift. After editing anything under `contracts/`: + +```bash +dart run tool/contracts_gen.dart # regenerates packages/stacflow/lib/src/generated +dart format packages/stacflow/lib/src/generated +``` + +Never hand-edit the generated directory: the analyzer excludes it and CI regenerates it. + +## Releases + +Per-package tags: `flow_ui-v` publishes `packages/flow_ui` and deploys the docs site (`.github/workflows/publish.yml`). Before tagging, bump `version:` in `packages/flow_ui/pubspec.yaml`, the `flow_ui:` constraint in `packages/flow_ui/example`, `playground` and `packages/stacflow` (pre-1.0 caret ranges), and `CHANGELOG.md`. `stacflow-v` publishes `packages/stacflow` the same way; before tagging, bump `version:` in `packages/stacflow/pubspec.yaml`, the `stacflow:` constraint in `packages/stacflow/example`, and its `CHANGELOG.md`. The pana gate wants a perfect score from both packages. + ## Commits Commit messages are conventional commits with a brief summary line: diff --git a/CLAUDE.md b/CLAUDE.md index 19940f1..ebb6e39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## What this is -**flow_ui** is a chat/assistant UI component library for Flutter — the presentation layer for AI assistant interfaces. It is a plain Flutter package (no codegen, no melos). Downstream packages depend on the public API exported from `lib/flow_ui.dart`, so treat it as a compatibility surface. +**flow_ui** is a chat/assistant UI component library for Flutter — the presentation layer for AI assistant interfaces. It is a plain Flutter package (no codegen) at `packages/flow_ui/`, inside a pub workspace driven by melos. Downstream packages depend on the public API exported from `lib/flow_ui.dart`, so treat it as a compatibility surface. Two hard constraints shape everything here: @@ -15,19 +15,26 @@ The theme, the conversation components (message, thread, streaming text, actions ## Layout -- Package at the repo root: `lib/`, `test/`, `pubspec.yaml`. -- `playground/` — the Flow UI Playground: a full Flutter app depending on the package via `path: ../`. Use it to demo and manually exercise components (every component has a stage demo, with variant pills and code snippets). +- Root `pubspec.yaml` is the pub workspace (members under `workspace:`) with the melos scripts; root `analysis_options.yaml` (very_good_analysis) governs `tool/` and the SDK package only. +- `packages/flow_ui/` — the published package: `lib/`, `example/` (the README's chat screen against Gemini), `assets/`, its own flutter_lints `analysis_options.yaml` and `.pubignore`. +- `packages/stacflow/` — the StacFlow SDK package (see "SDK package"), with `example/` (the README's chat screen against Gemini; flutter_lints like the flow_ui example). +- `playground/` — the Flow UI Playground: a full Flutter app and workspace member depending on `flow_ui: ^0.4.0`. Use it to demo and manually exercise components (every component has a stage demo, with variant pills and code snippets). +- `docs/` — the Astro site behind flowui.stac.dev. `contracts/` and `tool/` — the SDK wire contract and its codegen. ## Commands -**Don't write tests for now.** `test/` is deliberately empty — the component surface is still being reshaped design-first, so tests written now would mostly encode values about to change. Verify a change with `flutter analyze` and by exercising it in the `playground/` app, not by adding a test file. If something seems to genuinely need one, say so and let the user decide. +**Don't write tests for now.** `packages/flow_ui` has no `test/` yet: the component surface is still being reshaped design-first, so tests written now would mostly encode values about to change. Verify a change with `flutter analyze` and by exercising it in the `playground/` app, not by adding a test file. If something seems to genuinely need one, say so and let the user decide. + +**Don't write comments unless asked.** No doc comments, file headers or inline explanations in new or edited code; the code and the commit message carry the intent. The one exception is a comment a lint requires (for example `document_ignores` above an `// ignore`), kept to one line. Public API dartdoc is written only when the user asks for it. From the repo root: ```bash -flutter analyze -dart format . -flutter test # all tests (see above — none yet) +flutter pub get # resolves the whole workspace +dart run melos run analyze # dart analyze --fatal-infos in every member +dart run melos run format +dart run melos run test # SDK package tests; flow_ui has none yet +cd packages/flow_ui && flutter analyze && flutter pub publish --dry-run ``` Playground app: @@ -38,6 +45,29 @@ flutter pub get flutter run -d chrome # or any device ``` +## SDK package + +`packages/stacflow` is the StacFlow SDK: `StacFlowChat` (the controller) and `StacFlowChatView` on flow_ui, wired to Gemini, OpenAI and Claude with the developer's own key. One entrypoint, `package:stacflow/stacflow.dart`, which also re-exports flow_ui. Layout: `src/chat` (controller, state, view, and the flow_ui-to-wire reduction in `wire_history.dart`), `src/transport` (the `TurnTransport` seam, `TurnRequest` and the wire types, ids, the SSE parser; pure Dart), `src/providers` (the interface, the shared HTTP runner, one adapter per provider; pure Dart), `src/tools` (`Tool`, the call records and `runToolLoop`, the client-side tool loop shared by the controller and the smoke script; pure Dart). Rules: + +- `stacflow` depends on `flow_ui`, never the reverse (CI grep). +- Adapters emit the generated `SseEvent` union: `start` first, one `done` last, `seq` from 0. The runner in `turn_runner.dart` owns HTTP, abort, timeouts, key scrubbing and the tool-call bookkeeping (ids, argument buffering, the upgrade of `complete()` to `done{awaiting_client_tools}`); an adapter only declares `TurnRequest.tools`, maps frames, encodes the wire tool parts in history and replays its own raw content within a turn where the provider requires it (Gemini signatures and ids, Claude thinking blocks). +- The loop in `tool_loop.dart` owns dispatch, approval, timeouts, abort and the continuation segments; the controller only renders blocks into parts, keeps `ChatState.toolCalls`, and answers confirmations. `contracts/` is unchanged by tools: provider call ids ride inside the `tc_` ids. +- The API key is a private field set on exactly one header, and never appears in URLs, logs, `toString` or error text. +- No tests for now. Verify with `dart analyze --fatal-infos`, the smoke script (`cd packages/stacflow && dart run --define=PROVIDER=gemini --define=GEMINI_API_KEY=... tool/smoke.dart`, also `anthropic` and `openai`; `--define=ABORT_AFTER_FIRST_DELTA=true` and `--define=IMAGE=path.png` exercise abort and image input; `--define=TOOLS=true` registers a `get_time` tool and runs the loop, with `TOOL_PERMISSION=destructive` and `DECLINE=true` for the approval paths), and the example app (`cd packages/stacflow/example && flutter run` with the key in `lib/env.dart`, copied from `lib/env.example.dart` and gitignored; the `stacflow-example` entry in `.claude/launch.json` serves it on port 8124). Keep the example the runnable form of the README quickstart, against Gemini only, with the `set_theme` tool as its one tool. + +`contracts/` is the wire contract (SSE events, error codes); CI regenerates the Dart from it and fails on drift. After editing anything under `contracts/`: + +```bash +dart run tool/contracts_gen.dart # regenerates packages/stacflow/lib/src/generated +dart format packages/stacflow/lib/src/generated +``` + +Never hand-edit the generated directory: the analyzer excludes it and CI regenerates it. + +## Releases + +Per-package tags: `flow_ui-v` publishes `packages/flow_ui` and deploys the docs site (`.github/workflows/publish.yml`). Before tagging, bump `version:` in `packages/flow_ui/pubspec.yaml`, the `flow_ui:` constraint in `packages/flow_ui/example`, `playground` and `packages/stacflow` (pre-1.0 caret ranges), and `CHANGELOG.md`. `stacflow-v` publishes `packages/stacflow` the same way; before tagging, bump `version:` in `packages/stacflow/pubspec.yaml`, the `stacflow:` constraint in `packages/stacflow/example`, and its `CHANGELOG.md`. The pana gate wants a perfect score from both packages. + ## Commits Commit messages are conventional commits with a brief summary line: diff --git a/README.md b/README.md index 017e687..0ebc4a4 100644 --- a/README.md +++ b/README.md @@ -1,311 +1,37 @@

- Flow UI logo + Flow UI logo

flow_ui

-

- pub package - pub points - github stars - license: MIT -

-

📚 Documentation · 🧩 Playground - · 🤓 API Reference - · 🗺️ Roadmap + · 📦 pub.dev

-Flow UI is an open-source Flutter UI library to build production-grade Chat & AI assistant interfaces. +Flutter UI library for AI chat interfaces, and the StacFlow SDK built on it.

-![The flow_ui chat surface](assets/chat_zero_light.png) - -> [!IMPORTANT] -> flow_ui is pre-1.0. The API is still settling, and minor releases may -> carry breaking changes — pin a minor version and read the -> [changelog](CHANGELOG.md) when upgrading. - -## What's in the box - -| Component | What it does | -|---|---| -| [`FlowChatView`](https://flowui.stac.dev/components/chat-view) | The full chat surface: bounded thread over a composer, centred at a readable width, with a zero state (greeting, lifted composer, starters), a jump-to-latest button, and drag-and-drop with a washed invitation overlay (web) | -| [`FlowThread`](https://flowui.stac.dev/components/message-thread) | Scrollable conversation: reads from the top, anchoring to the newest message once it outgrows the viewport; text selects across turns and copies with its line breaks | -| [`FlowThreadList`](https://flowui.stac.dev/components/thread-list) | Conversation history for a side panel — host-labeled sections, single selection by id, unread and pinned marks | -| [`FlowMessage`](https://flowui.stac.dev/components/message-thread) | One turn — ink-wash user bubble, plain assistant, error bubble, typed content parts | -| [`FlowStreamingText`](https://flowui.stac.dev/components/streaming-text) | Animated text reveal while a reply arrives | -| [`FlowThinkingIndicator`](https://flowui.stac.dev/components/thinking-indicator) | Turning, breathing asterisk with a shimmering label | -| [`FlowShimmerText`](https://flowui.stac.dev/components/shimmer-text) | Sweeping text highlight, static once settled | -| [`FlowCodeBlock`](https://flowui.stac.dev/components/code-block) | Fenced code with built-in synchronous highlighting, a header label, and a copy affordance — languages host-extensible | -| [`FlowMarkdown`](https://flowui.stac.dev/components/markdown) | Assistant prose typeset from a built-in parser — headings, emphasis, lists, quotes, tables, links, and fences composing the code block; assistant turns render it by default and it streams gracefully | -| [`FlowErrorState`](https://flowui.stac.dev/components/error-state) | Failure card with a host-written message and retry pill — failed turns render it automatically | -| [`FlowConfirmation`](https://flowui.stac.dev/components/confirmation) | Approval card — an asterisk-marked request with approve and reject buttons that settles into the outcome; confirmation parts render it in a thread | -| [`FlowToast`](https://flowui.stac.dev/components/toast) | Floating notice: a glyph, one wrapping line and a cross on a frosted card; `showFlowToast` floats it in the nearest Overlay, stacked and auto-dismissing, with a handle to dismiss it early | -| [`FlowTool`](https://flowui.stac.dev/components/tool) | Tool-call card: one status mark that settles from a turning asterisk into a check or an error glyph, the call's title and primary argument, and its input and output behind a disclosure; tool parts render it in a thread | -| [`FlowMessageActions`](https://flowui.stac.dev/components/message-actions) | Copy / regenerate / edit / feedback row under a message | -| [`FlowComposer`](https://flowui.stac.dev/components/composer) | Multiline input with send/stop, attachments strip, the platform's file dialog (`showFlowAttachmentPicker` from your own menu, or a built-in attach button), image paste and card-scoped drop (web), and leading/trailing action slots | -| [`FlowMenu`](https://flowui.stac.dev/components/menu) | Icon-triggered menu with groups, submenus, and toggles — anchored card on desktop, bottom sheet on phones | -| [`FlowModelSelector`](https://flowui.stac.dev/components/model-selector) | Model picker with effort and overflow submenus, sheet on phones | -| [`FlowPill`](https://flowui.stac.dev/components/pill) | Removable pill for an enabled tool or mode in the composer's action row — label auto-drops on phones | -| [`FlowAttachmentGroup`](https://flowui.stac.dev/components/attachments) | Image and file tiles with a type pill | -| [`FlowAttachmentPreview`](https://flowui.stac.dev/components/attachments) | Full-screen image viewer with zoom and paging | -| [`FlowDropTarget`](https://flowui.stac.dev/components/chat-view) | File-drop detection for any subtree (web) — decoded attachments out, hover state for your own treatment | -| [`FlowSuggestion`](https://flowui.stac.dev/components/suggestions) / [`FlowSuggestionGroup`](https://flowui.stac.dev/components/suggestions) | Prompt starters — plain or outlined; scroll, wrap, or column layouts | -| [`FlowGreeting`](https://flowui.stac.dev/components/greeting) | Zero-state headline | -| [`FlowTheme`](https://flowui.stac.dev/theming) | Design tokens (colors and typography) as a `ThemeExtension`, with light and dark presets | - -## Getting started - -```yaml -dependencies: - flow_ui: ^0.3.0 -``` - -Install the theme once (optional — without it, components fall back to a -preset matching the ambient brightness): +## Packages -```dart -MaterialApp( - theme: ThemeData(extensions: [FlowTheme.light()]), - darkTheme: ThemeData( - brightness: Brightness.dark, - extensions: [FlowTheme.dark()], - ), -) -``` - -The default typography — Google Sans and Google Sans Code — arrives through -[google_fonts](https://pub.dev/packages/google_fonts): each cut is fetched -on first use and cached on the device, so there is no font to bundle. The -fetch needs network access: `android.permission.INTERNET` in an Android -app's main manifest (Flutter's template grants it only to debug and profile) -and the `com.apple.security.network.client` entitlement in a sandboxed macOS -app; without it text falls back to the platform face. To render offline on -first launch, ship the files under a `google_fonts/` asset folder. - -## Build a chat screen - -Messages are pure view models. Your app maps its own transport into -`FlowMessageData`, and streaming is data, not streams: while a reply arrives, -rebuild with `copyWith` carrying the grown text. - -```dart -class ChatPage extends StatefulWidget { - const ChatPage({super.key}); +This repository is a Dart pub workspace. - @override - State createState() => _ChatPageState(); -} +| Package | What it is | Status | +|---|---|---| +| [`packages/flow_ui`](packages/flow_ui) | The chat and assistant UI component library | [![pub package](https://img.shields.io/pub/v/flow_ui.svg)](https://pub.dev/packages/flow_ui) | +| [`packages/stacflow`](packages/stacflow) | Streaming AI chat on flow_ui with Gemini, OpenAI and Claude using your own key: `StacFlowChat` and `StacFlowChatView` | Pre-release, not published | -class _ChatPageState extends State { - final ScrollController _scroll = ScrollController(); - List _messages = const []; - bool _generating = false; - - void _send(String text) async { - final id = DateTime.now().microsecondsSinceEpoch.toString(); - setState(() { - _messages = [ - ..._messages, - FlowMessageData.text(id: id, role: FlowMessageRole.user, text: text), - // An empty pending reply renders the thinking indicator. - FlowMessageData( - id: '$id-reply', - role: FlowMessageRole.assistant, - status: FlowMessageStatus.pending, - ), - ]; - _generating = true; - }); - - // Feed chunks from your backend as they arrive. - var streamed = ''; - await for (final chunk in myBackend.reply(text)) { - streamed += chunk; - setState(() { - _messages = [ - ..._messages.sublist(0, _messages.length - 1), - _messages.last.copyWith( - parts: [FlowTextPart(streamed)], - status: FlowMessageStatus.streaming, - ), - ]; - }); - } - - setState(() { - _messages = [ - ..._messages.sublist(0, _messages.length - 1), - _messages.last.copyWith(status: FlowMessageStatus.complete), - ]; - _generating = false; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: FlowChatView( - empty: _messages.isEmpty, - greeting: const FlowGreeting( - icon: Icons.wb_twilight, - text: 'Good afternoon', - ), - suggestions: FlowSuggestionGroup( - layout: FlowSuggestionLayout.column, - suggestions: [ - FlowSuggestion( - label: 'Write an essay about life and enjoyment', - icon: Icons.edit_note, - onTap: () => _send('Write an essay about life and enjoyment'), - ), - FlowSuggestion( - label: 'Create a Monday briefing from my tasks', - icon: Icons.event_available, - onTap: () => _send('Create a Monday briefing from my tasks'), - ), - ], - ), - thread: FlowThread( - messages: _messages, - controller: _scroll, - thinkingLabel: 'Thinking…', - ), - threadController: _scroll, - jumpToLatestTooltip: 'Jump to latest', - composer: FlowComposer( - placeholder: 'How can I help you today?', - isStreaming: _generating, - onSend: _send, - onStop: myBackend.stop, - ), - ), - ); - } -} -``` +Also here: [`playground/`](playground), every component on a stage, hosted at [flowui.stac.dev/playground](https://flowui.stac.dev/playground); [`docs/`](docs), the site; [`contracts/`](contracts), the SDK wire contract, with its codegen in [`tool/`](tool). -`FlowChatView` is body-only — it builds no `Scaffold` and no app bar, so -your app keeps the chrome, the background, and the keyboard inset. See -[`example/lib/main.dart`](example/lib/main.dart) for a complete runnable -version of this page, and the [live playground](https://flowui.stac.dev/playground) -for a demo of every component with variants and code snippets. - -## Composer accessories - -The composer takes leading and trailing action slots. Drop in a `FlowMenu` -(attachments, toggles) and a `FlowModelSelector` — both render an anchored -card on wide layouts and a bottom sheet on phones: - -```dart -FlowComposer( - onSend: _send, - leadingActions: [ - FlowMenu( - icon: Icons.add, - tooltip: 'Add to chat', - entries: const [ - FlowMenuOption(id: 'files', icon: Icons.attach_file, label: 'Add files'), - FlowMenuDivider(), - FlowMenuOption(id: 'web', icon: Icons.public, label: 'Web search', selected: true), - ], - onSelected: _handleMenu, - ), - ], - trailingActions: [ - FlowModelSelector( - models: const [ - FlowModelOption(id: 'fast', label: 'Fast', description: 'Quick answers'), - FlowModelOption(id: 'smart', label: 'Smart', description: 'Hard problems'), - ], - selectedId: _modelId, - onSelected: (id) => setState(() => _modelId = id), - ), - ], -) -``` - -## Message content is typed parts - -A message holds an ordered list of sealed `FlowMessagePart`s — -`FlowTextPart`, `FlowAttachmentPart`, and `FlowCustomPart` for anything the -package doesn't know about. Custom parts render through a builder you supply, -so hosts can inject arbitrary widgets (tool cards, citations, charts) without -forking the message renderer: - -```dart -FlowThread( - messages: _messages, - customPartBuilder: (context, message, part) { - return switch (part.type) { - 'order-card' => OrderCard(order: part.data as Order), - _ => null, // unknown parts are skipped - }; - }, -) -``` - -Attachments carry an `ImageProvider`, so network, file, memory, and asset -images all work. Build them yourself, or let the package hand them to you -ready-made — `showFlowAttachmentPicker` opens the platform's file dialog -from your own menu (or the composer's built-in button does), and on the -web the surface takes drops and the field takes pasted images: - -```dart -FlowMessageData( - id: 'm1', - role: FlowMessageRole.user, - parts: [ - FlowAttachmentPart([ - FlowAttachment(id: 'a1', thumbnail: NetworkImage(url), kind: 'JPG', label: 'sunset.jpg'), - ]), - FlowTextPart('What do you think of this shot?'), - ], -) -``` - -## Theming - -`FlowTheme` carries two token sets — colors and typography. Role names follow -Material 3's `ColorScheme`, so an existing scheme maps across, with one -addition: the design draws content at three ink levels (`onSurface`, -`onSurfaceVariant`, `onSurfaceMuted`) where M3 names two. Start from a preset -and override what your brand needs: - -```dart -FlowTheme( - colors: FlowColors.dark.copyWith(primary: const Color(0xFF6C5CE7)), - typography: FlowTypography.standard, -) -``` - -Spacing and corner radii are deliberately not tokens. Following Material's -structure, each component bakes its own metrics from the Flow UI design file -and exposes per-widget overrides (`padding:`, `borderRadius:`) where hosts -retheme. Strings shown to the user (tooltips, placeholders, labels) are -host-supplied, so localization stays in your app — the one exception, the -model selector's `effortLabel` and `moreModelsLabel` English defaults, is -overridable the same way. - -## Docs & playground - -Full documentation lives at [flowui.stac.dev](https://flowui.stac.dev/), and -every component has a stage in the -[live playground](https://flowui.stac.dev/playground) — variant pills and -code snippets included. The playground is also -[in the repo](https://github.com/StacDev/flow_ui/tree/main/playground) to run -locally: +## Development ```bash +flutter pub get # resolves the whole workspace +dart run melos run analyze +dart run melos run test cd playground && flutter run -d chrome ``` -## License - -Code is released under the [MIT License](LICENSE). Google Sans and Google -Sans Code are fetched from Google Fonts under the -[SIL Open Font License](https://openfontlicense.org), not bundled. +Package docs: [flow_ui README](packages/flow_ui/README.md). Licensed under [MIT](LICENSE). diff --git a/analysis_options.yaml b/analysis_options.yaml index 534f26d..376dd57 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,13 +1,15 @@ +# Governs tool/ and the stacflow package. packages/flow_ui, its example and +# playground keep their own flutter_lints files (nearest file wins). +include: package:very_good_analysis/analysis_options.yaml + analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true exclude: - - build/** - - android/** - - ios/** - - web/** - - windows/** - - macos/** - - linux/** -include: package:flutter_lints/flutter.yaml - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + - "**/build/**" + - "**/lib/src/generated/**" + - docs/** + errors: + public_member_api_docs: ignore diff --git a/contracts/README.md b/contracts/README.md new file mode 100644 index 0000000..44453a0 --- /dev/null +++ b/contracts/README.md @@ -0,0 +1,15 @@ +# contracts/ + +The stacflow wire contract: the event stream a transport produces for one +model segment, and the error codes it can carry. `tool/contracts_gen.dart` +generates `packages/stacflow/lib/src/generated/` from these files. The +generated directory is never hand-edited; CI regenerates it and fails on +drift. + +| File | What it defines | +|---|---| +| `sse-events.schema.json` | The SSE streaming protocol: six events, framing rules, ordering invariants | +| `error-codes.json` | The error-code registry with failure-class attribution | + +Evolution is additive: new event names, new optional fields, new error +codes. Decoders never throw on unknown events or fields. diff --git a/contracts/error-codes.json b/contracts/error-codes.json new file mode 100644 index 0000000..182bdb7 --- /dev/null +++ b/contracts/error-codes.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "description": "The additively growing error-code registry. `failure_class` says who failed; `http` is the status an HTTP transport would use; SSE error events carry the same code and class. Clients map `code` to localized strings; `message` on the wire is user-safe English.", + "codes": { + "validation_failed": { "failure_class": "gateway", "http": 422, "retryable": false, "description": "The request failed validation before it was sent." }, + "provider_error": { "failure_class": "provider", "http": 502, "retryable": true, "description": "The model provider failed; upstream carries {provider, status, code}." }, + "provider_rate_limited": { "failure_class": "provider", "http": 429, "retryable": true, "description": "The developer's provider key is rate-limited or out of quota upstream." }, + "provider_auth_failed": { "failure_class": "provider", "http": 502, "retryable": false, "description": "The provider rejected the API key." }, + "segment_timeout": { "failure_class": "timeout", "http": 504, "retryable": true, "description": "The provider stream stalled past the segment deadline." }, + "cancelled": { "failure_class": "cancelled", "http": 499, "retryable": false, "description": "The client stopped the stream; the segment ends done{cancelled}." } + } +} diff --git a/contracts/sse-events.schema.json b/contracts/sse-events.schema.json new file mode 100644 index 0000000..6688de3 --- /dev/null +++ b/contracts/sse-events.schema.json @@ -0,0 +1,171 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://stacflow.ai/contracts/sse-events.schema.json", + "title": "StacFlow SSE protocol v1", + "description": "The normalized event stream a transport produces for one model segment. Framing: Content-Type text/event-stream; the SSE `event:` field carries the event name; `data:` carries exactly one JSON object on one line; the SSE `id:` field carries the decimal `seq`. `seq` is 0-based and strictly monotonic per stream with no gaps. Stream-scoped identity is stated once in `start` and never repeated. Every stream ends with exactly one `done`; a socket close without `done` is a retryable transport failure, never a protocol state. The Dart provider adapters emit this event union directly. Evolution is additive-only; decoders must ignore unknown event names and unknown fields.", + "x-stacflow-protocol": { + "version": 1, + "events": { + "start": "#/$defs/StartEvent", + "delta": "#/$defs/DeltaEvent", + "tool_call": "#/$defs/ToolCallEvent", + "usage": "#/$defs/UsageEvent", + "error": "#/$defs/ErrorEvent", + "done": "#/$defs/DoneEvent" + }, + "ordering": [ + "Per tool call: phase start, then zero or more phase delta, then phase end.", + "Assistant text may interleave BETWEEN tool calls, never inside one call's phases.", + "Tool calls are serialized in v1: no overlapping starts.", + "`error` is non-terminal by itself and is always followed by `done`.", + "`done` is always last and occurs exactly once." + ] + }, + "oneOf": [ + { "$ref": "#/$defs/StartEvent" }, + { "$ref": "#/$defs/DeltaEvent" }, + { "$ref": "#/$defs/ToolCallEvent" }, + { "$ref": "#/$defs/UsageEvent" }, + { "$ref": "#/$defs/ErrorEvent" }, + { "$ref": "#/$defs/DoneEvent" } + ], + "$defs": { + "Seq": { + "type": "integer", + "minimum": 0, + "description": "0-based, strictly monotonic per stream, no gaps. Mirrored in the SSE id: field." + }, + "TurnId": { "type": "string", "pattern": "^trn_[A-Za-z0-9]+$" }, + "ThreadId": { "type": "string", "pattern": "^thr_[A-Za-z0-9]+$" }, + "MessageId": { "type": "string", "pattern": "^msg_[A-Za-z0-9]+$" }, + "ToolCallId": { "type": "string", "pattern": "^tc_[A-Za-z0-9]+$" }, + "AgentVersionId": { "type": "string", "pattern": "^agv_[A-Za-z0-9]+$" }, + "FailureClass": { + "type": "string", + "enum": ["provider", "developer_api", "gateway", "policy", "timeout", "cancelled"], + "description": "The wire-level attribution enum (D18), shared by SSE error events, tool_results entries, and analytics." + }, + "Upstream": { + "type": "object", + "description": "Structured upstream attribution: which provider/backend actually failed, regardless of the observed HTTP status.", + "properties": { + "provider": { "type": "string" }, + "status": { "type": "integer" }, + "code": { "type": "string" } + }, + "additionalProperties": true + }, + "StartEvent": { + "type": "object", + "description": "First event of every stream (seq 0). Declares stream-scoped identity once. `segment` is the 0-based continuation index within the turn; `message_id` is the assistant message being built, stable across all segments of the turn.", + "properties": { + "seq": { "$ref": "#/$defs/Seq" }, + "protocol": { "const": 1 }, + "turn_id": { "$ref": "#/$defs/TurnId" }, + "segment": { "type": "integer", "minimum": 0 }, + "message_id": { "$ref": "#/$defs/MessageId" }, + "thread_id": { "$ref": "#/$defs/ThreadId" }, + "model": { "type": "string" }, + "agent_version_id": { "$ref": "#/$defs/AgentVersionId" } + }, + "required": ["seq", "protocol", "turn_id", "segment", "message_id", "thread_id", "model", "agent_version_id"], + "additionalProperties": true + }, + "DeltaEvent": { + "type": "object", + "description": "Assistant text delta.", + "properties": { + "seq": { "$ref": "#/$defs/Seq" }, + "text": { "type": "string" } + }, + "required": ["seq", "text"], + "additionalProperties": true + }, + "ToolCallEvent": { + "type": "object", + "description": "One tool call, streamed in three phases. `args` at phase end is complete canonical JSON and is authoritative over concatenated deltas. `permission_class` comes from the registered tool so the SDK can stage the confirmation card before the arguments finish streaming. `executor` is \"client\" in v1 (\"server\" reserved).", + "oneOf": [ + { + "type": "object", + "properties": { + "seq": { "$ref": "#/$defs/Seq" }, + "phase": { "const": "start" }, + "tool_call_id": { "$ref": "#/$defs/ToolCallId" }, + "tool_name": { "type": "string" }, + "executor": { "type": "string", "enum": ["client", "server"] }, + "permission_class": { "type": "string", "enum": ["read", "write", "destructive"] } + }, + "required": ["seq", "phase", "tool_call_id", "tool_name", "executor", "permission_class"], + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "seq": { "$ref": "#/$defs/Seq" }, + "phase": { "const": "delta" }, + "tool_call_id": { "$ref": "#/$defs/ToolCallId" }, + "args_delta": { "type": "string", "description": "Raw JSON fragment; enables progressive argument rendering. Consumers may ignore deltas entirely." } + }, + "required": ["seq", "phase", "tool_call_id", "args_delta"], + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "seq": { "$ref": "#/$defs/Seq" }, + "phase": { "const": "end" }, + "tool_call_id": { "$ref": "#/$defs/ToolCallId" }, + "args": { "type": "object", "additionalProperties": true } + }, + "required": ["seq", "phase", "tool_call_id", "args"], + "additionalProperties": true + } + ] + }, + "UsageEvent": { + "type": "object", + "description": "One per provider call; additive across a stream.", + "properties": { + "seq": { "$ref": "#/$defs/Seq" }, + "provider_call": { "type": "integer", "minimum": 0 }, + "model": { "type": "string" }, + "input_tokens": { "type": "integer", "minimum": 0 }, + "output_tokens": { "type": "integer", "minimum": 0 } + }, + "required": ["seq", "provider_call", "model", "input_tokens", "output_tokens"], + "additionalProperties": true + }, + "ErrorEvent": { + "type": "object", + "description": "Non-terminal by itself; always followed by done. `code` comes from error-codes.json (additively growing); `message` is user-safe English — clients map `code` to localized strings.", + "properties": { + "seq": { "$ref": "#/$defs/Seq" }, + "failure_class": { "$ref": "#/$defs/FailureClass" }, + "code": { "type": "string" }, + "message": { "type": "string" }, + "retryable": { "type": "boolean" }, + "upstream": { "$ref": "#/$defs/Upstream" } + }, + "required": ["seq", "failure_class", "code", "message", "retryable"], + "additionalProperties": true + }, + "DoneEvent": { + "type": "object", + "description": "Always last, exactly once. `awaiting_client_tools` ends the segment pending client execution; the turn continues via a tool_results POST.", + "properties": { + "seq": { "$ref": "#/$defs/Seq" }, + "status": { "type": "string", "enum": ["complete", "awaiting_client_tools", "error", "cancelled"] }, + "turn_id": { "$ref": "#/$defs/TurnId" }, + "pending_tool_call_ids": { + "type": "array", + "items": { "$ref": "#/$defs/ToolCallId" }, + "minItems": 1 + } + }, + "required": ["seq", "status", "turn_id"], + "if": { "properties": { "status": { "const": "awaiting_client_tools" } } }, + "then": { "required": ["seq", "status", "turn_id", "pending_tool_call_ids"] }, + "additionalProperties": true + } + } +} diff --git a/docs/README.md b/docs/README.md index ed869b1..a0d29e0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,7 @@ npm run build # static site in dist/ Google Sans loads from Google Fonts via the `` in `astro.config.mjs` — the same source the package fetches from at runtime. Brand colors in `src/styles/theme.css` mirror the package's design tokens in -`lib/src/theme/flow_colors.dart`. +`packages/flow_ui/lib/src/theme/flow_colors.dart`. `npm run playground` builds the repo's `playground/` Flutter app for the web into `public/playground/` — served at `/playground/` (path diff --git a/docs/src/content/docs/getting-started.mdx b/docs/src/content/docs/getting-started.mdx index f32c335..a3ef287 100644 --- a/docs/src/content/docs/getting-started.mdx +++ b/docs/src/content/docs/getting-started.mdx @@ -20,7 +20,7 @@ One line in your pubspec: ```yaml title="pubspec.yaml" dependencies: - flow_ui: ^0.3.0 + flow_ui: ^0.4.0 ``` That brings in the Flutter SDK, `material_ui`, `file_selector` — the @@ -94,9 +94,10 @@ snippets included. It is also in the repo to run locally: cd playground && flutter run -d chrome ``` -For a minimal starting point, the repo's `example/` is the full chat screen -from the README as a single-file app: generate platform runners once with -`flutter create . --platforms=web`, then `flutter run -d chrome`. +For a minimal starting point, the repo's `packages/flow_ui/example/` is the +full chat screen from the README as a single-file app, platform runners +checked in: `cd packages/flow_ui/example`, then +`flutter run -d chrome --dart-define=GEMINI_API_KEY=...`. ## Next diff --git a/example/pubspec.lock b/example/pubspec.lock deleted file mode 100644 index 2a60bc3..0000000 --- a/example/pubspec.lock +++ /dev/null @@ -1,542 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" - source: hosted - version: "2.13.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" - source: hosted - version: "1.4.1" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - code_assets: - dependency: transitive - description: - name: code_assets - sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: f141ea4f277af142a0356955707f6556f37b03947d39d55585981a06ca437bd6 - url: "https://pub.dev" - source: hosted - version: "0.3.5+5" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" - cupertino_ui: - dependency: transitive - description: - name: cupertino_ui - sha256: "7ed8ce4159d342eec4c65f4ea6eec57adaf9365404378541f38efc1da20a5b3d" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - file_selector: - dependency: transitive - description: - name: file_selector - sha256: bd15e43e9268db636b53eeaca9f56324d1622af30e5c34d6e267649758c84d9a - url: "https://pub.dev" - source: hosted - version: "1.1.0" - file_selector_android: - dependency: transitive - description: - name: file_selector_android - sha256: "1d45e9910f68c16eb0c74f0b10097ad81aed516ea28054c027137e8f7d75e840" - url: "https://pub.dev" - source: hosted - version: "0.5.2+9" - file_selector_ios: - dependency: transitive - description: - name: file_selector_ios - sha256: e2ecf2885c121691ce13b60db3508f53c01f869fb6e8dc5c1cfa771e4c46aeca - url: "https://pub.dev" - source: hosted - version: "0.5.3+5" - file_selector_linux: - dependency: transitive - description: - name: file_selector_linux - sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" - url: "https://pub.dev" - source: hosted - version: "0.9.4" - file_selector_macos: - dependency: transitive - description: - name: file_selector_macos - sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" - url: "https://pub.dev" - source: hosted - version: "0.9.5" - file_selector_platform_interface: - dependency: transitive - description: - name: file_selector_platform_interface - sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" - url: "https://pub.dev" - source: hosted - version: "2.7.0" - file_selector_web: - dependency: transitive - description: - name: file_selector_web - sha256: "73181fbc5257776d8ecaa6a94ab3c8e920ad143b9132a6d984a9271dfc6928d3" - url: "https://pub.dev" - source: hosted - version: "0.9.5" - file_selector_windows: - dependency: transitive - description: - name: file_selector_windows - sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" - url: "https://pub.dev" - source: hosted - version: "0.9.3+5" - flow_ui: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.2.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_localizations: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - google_fonts: - dependency: transitive - description: - name: google_fonts - sha256: e3cb3ee6b47fd2472c23de6da5744796a4da195137759ddb3fbcc9467b7b3c7d - url: "https://pub.dev" - source: hosted - version: "8.2.1" - hooks: - dependency: transitive - description: - name: hooks - sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - http: - dependency: "direct main" - description: - name: http - sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" - source: hosted - version: "1.6.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - intl: - dependency: transitive - description: - name: intl - sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" - url: "https://pub.dev" - source: hosted - version: "0.20.3" - jni: - dependency: transitive - description: - name: jni - sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 - url: "https://pub.dev" - source: hosted - version: "1.0.3" - jni_flutter: - dependency: transitive - description: - name: jni_flutter - sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - jni_util: - dependency: transitive - description: - name: jni_util - sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" - url: "https://pub.dev" - source: hosted - version: "0.12.20" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" - source: hosted - version: "0.13.0" - material_ui: - dependency: "direct main" - description: - name: material_ui - sha256: "4f3f38b9953df0a87d6bf5f21880029f77c47048487d5339410c39936be4683b" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - meta: - dependency: transitive - description: - name: meta - sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" - url: "https://pub.dev" - source: hosted - version: "1.19.0" - objective_c: - dependency: transitive - description: - name: objective_c - sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e - url: "https://pub.dev" - source: hosted - version: "9.5.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d - url: "https://pub.dev" - source: hosted - version: "3.0.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 - url: "https://pub.dev" - source: hosted - version: "2.1.6" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" - url: "https://pub.dev" - source: hosted - version: "2.3.1" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" - source: hosted - version: "2.6.0" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" - url: "https://pub.dev" - source: hosted - version: "2.2.2" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" - url: "https://pub.dev" - source: hosted - version: "2.1.3" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - record_use: - dependency: transitive - description: - name: record_use - sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" - url: "https://pub.dev" - source: hosted - version: "0.6.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" - source: hosted - version: "1.10.2" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test_api: - dependency: transitive - description: - name: test_api - sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" - url: "https://pub.dev" - source: hosted - version: "0.7.12" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" - url: "https://pub.dev" - source: hosted - version: "15.3.0" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" -sdks: - dart: ">=3.12.2 <4.0.0" - flutter: ">=3.44.0" diff --git a/.metadata b/packages/flow_ui/.metadata similarity index 100% rename from .metadata rename to packages/flow_ui/.metadata diff --git a/packages/flow_ui/.pubignore b/packages/flow_ui/.pubignore new file mode 100644 index 0000000..a70b1b1 --- /dev/null +++ b/packages/flow_ui/.pubignore @@ -0,0 +1,25 @@ +# Replaces .gitignore for publishing. Unrooted so example/ stays covered when +# publishing from outside a git checkout. +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ +*.iml +*.ipr +*.iws +.idea/ +.env +.metadata +pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins-dependencies +build/ +coverage/ +doc/ diff --git a/CHANGELOG.md b/packages/flow_ui/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to packages/flow_ui/CHANGELOG.md diff --git a/packages/flow_ui/LICENSE b/packages/flow_ui/LICENSE new file mode 100644 index 0000000..ee6ff7f --- /dev/null +++ b/packages/flow_ui/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Stac + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/flow_ui/README.md b/packages/flow_ui/README.md new file mode 100644 index 0000000..78dcce7 --- /dev/null +++ b/packages/flow_ui/README.md @@ -0,0 +1,311 @@ +

+ Flow UI logo +

+ +

flow_ui

+ +

+ pub package + pub points + github stars + license: MIT +

+ +

+ 📚 Documentation + · 🧩 Playground + · 🤓 API Reference + · 🗺️ Roadmap +

+ +

+Flow UI is an open-source Flutter UI library to build production-grade Chat & AI assistant interfaces. +

+ +![The flow_ui chat surface](assets/chat_zero_light.png) + +> [!IMPORTANT] +> flow_ui is pre-1.0. The API is still settling, and minor releases may +> carry breaking changes — pin a minor version and read the +> [changelog](CHANGELOG.md) when upgrading. + +## What's in the box + +| Component | What it does | +|---|---| +| [`FlowChatView`](https://flowui.stac.dev/components/chat-view) | The full chat surface: bounded thread over a composer, centred at a readable width, with a zero state (greeting, lifted composer, starters), a jump-to-latest button, and drag-and-drop with a washed invitation overlay (web) | +| [`FlowThread`](https://flowui.stac.dev/components/message-thread) | Scrollable conversation: reads from the top, anchoring to the newest message once it outgrows the viewport; text selects across turns and copies with its line breaks | +| [`FlowThreadList`](https://flowui.stac.dev/components/thread-list) | Conversation history for a side panel — host-labeled sections, single selection by id, unread and pinned marks | +| [`FlowMessage`](https://flowui.stac.dev/components/message-thread) | One turn — ink-wash user bubble, plain assistant, error bubble, typed content parts | +| [`FlowStreamingText`](https://flowui.stac.dev/components/streaming-text) | Animated text reveal while a reply arrives | +| [`FlowThinkingIndicator`](https://flowui.stac.dev/components/thinking-indicator) | Turning, breathing asterisk with a shimmering label | +| [`FlowShimmerText`](https://flowui.stac.dev/components/shimmer-text) | Sweeping text highlight, static once settled | +| [`FlowCodeBlock`](https://flowui.stac.dev/components/code-block) | Fenced code with built-in synchronous highlighting, a header label, and a copy affordance — languages host-extensible | +| [`FlowMarkdown`](https://flowui.stac.dev/components/markdown) | Assistant prose typeset from a built-in parser — headings, emphasis, lists, quotes, tables, links, and fences composing the code block; assistant turns render it by default and it streams gracefully | +| [`FlowErrorState`](https://flowui.stac.dev/components/error-state) | Failure card with a host-written message and retry pill — failed turns render it automatically | +| [`FlowConfirmation`](https://flowui.stac.dev/components/confirmation) | Approval card — an asterisk-marked request with approve and reject buttons that settles into the outcome; confirmation parts render it in a thread | +| [`FlowToast`](https://flowui.stac.dev/components/toast) | Floating notice: a glyph, one wrapping line and a cross on a frosted card; `showFlowToast` floats it in the nearest Overlay, stacked and auto-dismissing, with a handle to dismiss it early | +| [`FlowTool`](https://flowui.stac.dev/components/tool) | Tool-call card: one status mark that settles from a turning asterisk into a check or an error glyph, the call's title and primary argument, and its input and output behind a disclosure; tool parts render it in a thread | +| [`FlowMessageActions`](https://flowui.stac.dev/components/message-actions) | Copy / regenerate / edit / feedback row under a message | +| [`FlowComposer`](https://flowui.stac.dev/components/composer) | Multiline input with send/stop, attachments strip, the platform's file dialog (`showFlowAttachmentPicker` from your own menu, or a built-in attach button), image paste and card-scoped drop (web), and leading/trailing action slots | +| [`FlowMenu`](https://flowui.stac.dev/components/menu) | Icon-triggered menu with groups, submenus, and toggles — anchored card on desktop, bottom sheet on phones | +| [`FlowModelSelector`](https://flowui.stac.dev/components/model-selector) | Model picker with effort and overflow submenus, sheet on phones | +| [`FlowPill`](https://flowui.stac.dev/components/pill) | Removable pill for an enabled tool or mode in the composer's action row — label auto-drops on phones | +| [`FlowAttachmentGroup`](https://flowui.stac.dev/components/attachments) | Image and file tiles with a type pill | +| [`FlowAttachmentPreview`](https://flowui.stac.dev/components/attachments) | Full-screen image viewer with zoom and paging | +| [`FlowDropTarget`](https://flowui.stac.dev/components/chat-view) | File-drop detection for any subtree (web) — decoded attachments out, hover state for your own treatment | +| [`FlowSuggestion`](https://flowui.stac.dev/components/suggestions) / [`FlowSuggestionGroup`](https://flowui.stac.dev/components/suggestions) | Prompt starters — plain or outlined; scroll, wrap, or column layouts | +| [`FlowGreeting`](https://flowui.stac.dev/components/greeting) | Zero-state headline | +| [`FlowTheme`](https://flowui.stac.dev/theming) | Design tokens (colors and typography) as a `ThemeExtension`, with light and dark presets | + +## Getting started + +```yaml +dependencies: + flow_ui: ^0.4.0 +``` + +Install the theme once (optional — without it, components fall back to a +preset matching the ambient brightness): + +```dart +MaterialApp( + theme: ThemeData(extensions: [FlowTheme.light()]), + darkTheme: ThemeData( + brightness: Brightness.dark, + extensions: [FlowTheme.dark()], + ), +) +``` + +The default typography — Google Sans and Google Sans Code — arrives through +[google_fonts](https://pub.dev/packages/google_fonts): each cut is fetched +on first use and cached on the device, so there is no font to bundle. The +fetch needs network access: `android.permission.INTERNET` in an Android +app's main manifest (Flutter's template grants it only to debug and profile) +and the `com.apple.security.network.client` entitlement in a sandboxed macOS +app; without it text falls back to the platform face. To render offline on +first launch, ship the files under a `google_fonts/` asset folder. + +## Build a chat screen + +Messages are pure view models. Your app maps its own transport into +`FlowMessageData`, and streaming is data, not streams: while a reply arrives, +rebuild with `copyWith` carrying the grown text. + +```dart +class ChatPage extends StatefulWidget { + const ChatPage({super.key}); + + @override + State createState() => _ChatPageState(); +} + +class _ChatPageState extends State { + final ScrollController _scroll = ScrollController(); + List _messages = const []; + bool _generating = false; + + void _send(String text) async { + final id = DateTime.now().microsecondsSinceEpoch.toString(); + setState(() { + _messages = [ + ..._messages, + FlowMessageData.text(id: id, role: FlowMessageRole.user, text: text), + // An empty pending reply renders the thinking indicator. + FlowMessageData( + id: '$id-reply', + role: FlowMessageRole.assistant, + status: FlowMessageStatus.pending, + ), + ]; + _generating = true; + }); + + // Feed chunks from your backend as they arrive. + var streamed = ''; + await for (final chunk in myBackend.reply(text)) { + streamed += chunk; + setState(() { + _messages = [ + ..._messages.sublist(0, _messages.length - 1), + _messages.last.copyWith( + parts: [FlowTextPart(streamed)], + status: FlowMessageStatus.streaming, + ), + ]; + }); + } + + setState(() { + _messages = [ + ..._messages.sublist(0, _messages.length - 1), + _messages.last.copyWith(status: FlowMessageStatus.complete), + ]; + _generating = false; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: FlowChatView( + empty: _messages.isEmpty, + greeting: const FlowGreeting( + icon: Icons.wb_twilight, + text: 'Good afternoon', + ), + suggestions: FlowSuggestionGroup( + layout: FlowSuggestionLayout.column, + suggestions: [ + FlowSuggestion( + label: 'Write an essay about life and enjoyment', + icon: Icons.edit_note, + onTap: () => _send('Write an essay about life and enjoyment'), + ), + FlowSuggestion( + label: 'Create a Monday briefing from my tasks', + icon: Icons.event_available, + onTap: () => _send('Create a Monday briefing from my tasks'), + ), + ], + ), + thread: FlowThread( + messages: _messages, + controller: _scroll, + thinkingLabel: 'Thinking…', + ), + threadController: _scroll, + jumpToLatestTooltip: 'Jump to latest', + composer: FlowComposer( + placeholder: 'How can I help you today?', + isStreaming: _generating, + onSend: _send, + onStop: myBackend.stop, + ), + ), + ); + } +} +``` + +`FlowChatView` is body-only — it builds no `Scaffold` and no app bar, so +your app keeps the chrome, the background, and the keyboard inset. See +[`example/lib/main.dart`](example/lib/main.dart) for a complete runnable +version of this page, and the [live playground](https://flowui.stac.dev/playground) +for a demo of every component with variants and code snippets. + +## Composer accessories + +The composer takes leading and trailing action slots. Drop in a `FlowMenu` +(attachments, toggles) and a `FlowModelSelector` — both render an anchored +card on wide layouts and a bottom sheet on phones: + +```dart +FlowComposer( + onSend: _send, + leadingActions: [ + FlowMenu( + icon: Icons.add, + tooltip: 'Add to chat', + entries: const [ + FlowMenuOption(id: 'files', icon: Icons.attach_file, label: 'Add files'), + FlowMenuDivider(), + FlowMenuOption(id: 'web', icon: Icons.public, label: 'Web search', selected: true), + ], + onSelected: _handleMenu, + ), + ], + trailingActions: [ + FlowModelSelector( + models: const [ + FlowModelOption(id: 'fast', label: 'Fast', description: 'Quick answers'), + FlowModelOption(id: 'smart', label: 'Smart', description: 'Hard problems'), + ], + selectedId: _modelId, + onSelected: (id) => setState(() => _modelId = id), + ), + ], +) +``` + +## Message content is typed parts + +A message holds an ordered list of sealed `FlowMessagePart`s — +`FlowTextPart`, `FlowAttachmentPart`, and `FlowCustomPart` for anything the +package doesn't know about. Custom parts render through a builder you supply, +so hosts can inject arbitrary widgets (tool cards, citations, charts) without +forking the message renderer: + +```dart +FlowThread( + messages: _messages, + customPartBuilder: (context, message, part) { + return switch (part.type) { + 'order-card' => OrderCard(order: part.data as Order), + _ => null, // unknown parts are skipped + }; + }, +) +``` + +Attachments carry an `ImageProvider`, so network, file, memory, and asset +images all work. Build them yourself, or let the package hand them to you +ready-made — `showFlowAttachmentPicker` opens the platform's file dialog +from your own menu (or the composer's built-in button does), and on the +web the surface takes drops and the field takes pasted images: + +```dart +FlowMessageData( + id: 'm1', + role: FlowMessageRole.user, + parts: [ + FlowAttachmentPart([ + FlowAttachment(id: 'a1', thumbnail: NetworkImage(url), kind: 'JPG', label: 'sunset.jpg'), + ]), + FlowTextPart('What do you think of this shot?'), + ], +) +``` + +## Theming + +`FlowTheme` carries two token sets — colors and typography. Role names follow +Material 3's `ColorScheme`, so an existing scheme maps across, with one +addition: the design draws content at three ink levels (`onSurface`, +`onSurfaceVariant`, `onSurfaceMuted`) where M3 names two. Start from a preset +and override what your brand needs: + +```dart +FlowTheme( + colors: FlowColors.dark.copyWith(primary: const Color(0xFF6C5CE7)), + typography: FlowTypography.standard, +) +``` + +Spacing and corner radii are deliberately not tokens. Following Material's +structure, each component bakes its own metrics from the Flow UI design file +and exposes per-widget overrides (`padding:`, `borderRadius:`) where hosts +retheme. Strings shown to the user (tooltips, placeholders, labels) are +host-supplied, so localization stays in your app — the one exception, the +model selector's `effortLabel` and `moreModelsLabel` English defaults, is +overridable the same way. + +## Docs & playground + +Full documentation lives at [flowui.stac.dev](https://flowui.stac.dev/), and +every component has a stage in the +[live playground](https://flowui.stac.dev/playground) — variant pills and +code snippets included. The playground is also +[in the repo](https://github.com/StacDev/flow_ui/tree/main/playground) to run +locally: + +```bash +cd playground && flutter run -d chrome +``` + +## License + +Code is released under the [MIT License](LICENSE). Google Sans and Google +Sans Code are fetched from Google Fonts under the +[SIL Open Font License](https://openfontlicense.org), not bundled. diff --git a/packages/flow_ui/analysis_options.yaml b/packages/flow_ui/analysis_options.yaml new file mode 100644 index 0000000..534f26d --- /dev/null +++ b/packages/flow_ui/analysis_options.yaml @@ -0,0 +1,13 @@ +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/assets/chat_zero_light.png b/packages/flow_ui/assets/chat_zero_light.png similarity index 100% rename from assets/chat_zero_light.png rename to packages/flow_ui/assets/chat_zero_light.png diff --git a/assets/flow_ui_logo.svg b/packages/flow_ui/assets/flow_ui_logo.svg similarity index 100% rename from assets/flow_ui_logo.svg rename to packages/flow_ui/assets/flow_ui_logo.svg diff --git a/example/.gitignore b/packages/flow_ui/example/.gitignore similarity index 89% rename from example/.gitignore rename to packages/flow_ui/example/.gitignore index bc3656c..c7d1b41 100644 --- a/example/.gitignore +++ b/packages/flow_ui/example/.gitignore @@ -23,8 +23,8 @@ migrate_working_dir/ # is commented out by default. #.vscode/ -# Your Gemini API key lives here — created by hand, never committed. -lib/env.g.dart +# Your Gemini API key, for --dart-define-from-file. Never committed. +env.json # Flutter/Dart/Pub related **/doc/api/ diff --git a/example/.metadata b/packages/flow_ui/example/.metadata similarity index 100% rename from example/.metadata rename to packages/flow_ui/example/.metadata diff --git a/example/README.md b/packages/flow_ui/example/README.md similarity index 58% rename from example/README.md rename to packages/flow_ui/example/README.md index c518c5e..b8dc394 100644 --- a/example/README.md +++ b/packages/flow_ui/example/README.md @@ -8,18 +8,22 @@ the selector, ask for a picture, and it comes back through `FlowImagePart`, shimmering until the bytes land. flow_ui renders the state; `gemini_api.dart` is the host-side transport it never sees. -Set up a key once — grab one from [Google AI Studio](https://aistudio.google.com/apikey) -and create `lib/env.g.dart` with it (the file is gitignored, so your key -stays on your machine): +Grab a key from [Google AI Studio](https://aistudio.google.com/apikey) and +pass it at launch. Platform runners are checked in, so it runs directly: -```dart -const String apiKey = 'AIza...'; +```bash +flutter run --dart-define=GEMINI_API_KEY=AIza... ``` -Platform runners are checked in, so it launches directly: +Or keep the key in a gitignored `env.json` next to this README and pass the +file instead: + +```json +{ "GEMINI_API_KEY": "AIza..." } +``` ```bash -flutter run +flutter run --dart-define-from-file=env.json ``` With an empty key the app still runs — sending just answers with the @@ -30,5 +34,9 @@ the hosted [playground](https://flowui.stac.dev/playground), or run it from [the repo](https://github.com/StacDev/flow_ui/tree/main/playground): ```bash -cd ../playground && flutter run -d chrome +cd playground && flutter run -d chrome # from the repo root ``` + +The example resolves through the repo's pub workspace (`resolution: workspace` +in its pubspec). To use it outside the repo, delete that line and depend on +`flow_ui` from pub.dev. diff --git a/example/analysis_options.yaml b/packages/flow_ui/example/analysis_options.yaml similarity index 100% rename from example/analysis_options.yaml rename to packages/flow_ui/example/analysis_options.yaml diff --git a/example/android/.gitignore b/packages/flow_ui/example/android/.gitignore similarity index 100% rename from example/android/.gitignore rename to packages/flow_ui/example/android/.gitignore diff --git a/example/android/app/build.gradle.kts b/packages/flow_ui/example/android/app/build.gradle.kts similarity index 100% rename from example/android/app/build.gradle.kts rename to packages/flow_ui/example/android/app/build.gradle.kts diff --git a/example/android/app/src/debug/AndroidManifest.xml b/packages/flow_ui/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from example/android/app/src/debug/AndroidManifest.xml rename to packages/flow_ui/example/android/app/src/debug/AndroidManifest.xml diff --git a/example/android/app/src/main/AndroidManifest.xml b/packages/flow_ui/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from example/android/app/src/main/AndroidManifest.xml rename to packages/flow_ui/example/android/app/src/main/AndroidManifest.xml diff --git a/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/flow_ui/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt similarity index 100% rename from example/android/app/src/main/kotlin/com/example/example/MainActivity.kt rename to packages/flow_ui/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/flow_ui/example/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from example/android/app/src/main/res/drawable-v21/launch_background.xml rename to packages/flow_ui/example/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/packages/flow_ui/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from example/android/app/src/main/res/drawable/launch_background.xml rename to packages/flow_ui/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flow_ui/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/flow_ui/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/flow_ui/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/flow_ui/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/flow_ui/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/flow_ui/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/flow_ui/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/flow_ui/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/flow_ui/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/flow_ui/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/example/android/app/src/main/res/values-night/styles.xml b/packages/flow_ui/example/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from example/android/app/src/main/res/values-night/styles.xml rename to packages/flow_ui/example/android/app/src/main/res/values-night/styles.xml diff --git a/example/android/app/src/main/res/values/styles.xml b/packages/flow_ui/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from example/android/app/src/main/res/values/styles.xml rename to packages/flow_ui/example/android/app/src/main/res/values/styles.xml diff --git a/example/android/app/src/profile/AndroidManifest.xml b/packages/flow_ui/example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from example/android/app/src/profile/AndroidManifest.xml rename to packages/flow_ui/example/android/app/src/profile/AndroidManifest.xml diff --git a/example/android/build.gradle.kts b/packages/flow_ui/example/android/build.gradle.kts similarity index 100% rename from example/android/build.gradle.kts rename to packages/flow_ui/example/android/build.gradle.kts diff --git a/example/android/gradle.properties b/packages/flow_ui/example/android/gradle.properties similarity index 100% rename from example/android/gradle.properties rename to packages/flow_ui/example/android/gradle.properties diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/flow_ui/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/flow_ui/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/example/android/settings.gradle.kts b/packages/flow_ui/example/android/settings.gradle.kts similarity index 100% rename from example/android/settings.gradle.kts rename to packages/flow_ui/example/android/settings.gradle.kts diff --git a/example/ios/.gitignore b/packages/flow_ui/example/ios/.gitignore similarity index 100% rename from example/ios/.gitignore rename to packages/flow_ui/example/ios/.gitignore diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/packages/flow_ui/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from example/ios/Flutter/AppFrameworkInfo.plist rename to packages/flow_ui/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/example/ios/Flutter/Debug.xcconfig b/packages/flow_ui/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from example/ios/Flutter/Debug.xcconfig rename to packages/flow_ui/example/ios/Flutter/Debug.xcconfig diff --git a/example/ios/Flutter/Release.xcconfig b/packages/flow_ui/example/ios/Flutter/Release.xcconfig similarity index 100% rename from example/ios/Flutter/Release.xcconfig rename to packages/flow_ui/example/ios/Flutter/Release.xcconfig diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/packages/flow_ui/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from example/ios/Runner.xcodeproj/project.pbxproj rename to packages/flow_ui/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/flow_ui/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/flow_ui/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flow_ui/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flow_ui/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flow_ui/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/flow_ui/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flow_ui/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/flow_ui/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/flow_ui/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/flow_ui/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flow_ui/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flow_ui/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flow_ui/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/flow_ui/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/example/ios/Runner/AppDelegate.swift b/packages/flow_ui/example/ios/Runner/AppDelegate.swift similarity index 100% rename from example/ios/Runner/AppDelegate.swift rename to packages/flow_ui/example/ios/Runner/AppDelegate.swift diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/flow_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/flow_ui/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/flow_ui/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/packages/flow_ui/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from example/ios/Runner/Base.lproj/Main.storyboard rename to packages/flow_ui/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/example/ios/Runner/Info.plist b/packages/flow_ui/example/ios/Runner/Info.plist similarity index 100% rename from example/ios/Runner/Info.plist rename to packages/flow_ui/example/ios/Runner/Info.plist diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/packages/flow_ui/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from example/ios/Runner/Runner-Bridging-Header.h rename to packages/flow_ui/example/ios/Runner/Runner-Bridging-Header.h diff --git a/example/ios/Runner/SceneDelegate.swift b/packages/flow_ui/example/ios/Runner/SceneDelegate.swift similarity index 100% rename from example/ios/Runner/SceneDelegate.swift rename to packages/flow_ui/example/ios/Runner/SceneDelegate.swift diff --git a/example/ios/RunnerTests/RunnerTests.swift b/packages/flow_ui/example/ios/RunnerTests/RunnerTests.swift similarity index 100% rename from example/ios/RunnerTests/RunnerTests.swift rename to packages/flow_ui/example/ios/RunnerTests/RunnerTests.swift diff --git a/packages/flow_ui/example/lib/env.dart b/packages/flow_ui/example/lib/env.dart new file mode 100644 index 0000000..df9648b --- /dev/null +++ b/packages/flow_ui/example/lib/env.dart @@ -0,0 +1,5 @@ +/// The Gemini key, passed at launch so it never lands in a commit: +/// flutter run --dart-define=GEMINI_API_KEY=AIza... +/// or from a gitignored file: flutter run --dart-define-from-file=env.json +/// Empty when neither is given: the app runs and sending shows the error card. +const String apiKey = String.fromEnvironment('GEMINI_API_KEY'); diff --git a/example/lib/gemini_api.dart b/packages/flow_ui/example/lib/gemini_api.dart similarity index 98% rename from example/lib/gemini_api.dart rename to packages/flow_ui/example/lib/gemini_api.dart index b09c4fd..b6e0817 100644 --- a/example/lib/gemini_api.dart +++ b/packages/flow_ui/example/lib/gemini_api.dart @@ -65,8 +65,8 @@ class GeminiApi { Stream streamReply(List history) async* { if (apiKey.isEmpty) { throw GeminiApiException( - 'No API key. Paste yours into example/lib/env.g.dart — grab one ' - 'from Google AI Studio.', + 'No API key. Pass one with --dart-define=GEMINI_API_KEY=... ' + '(see example/README.md).', ); } diff --git a/example/lib/main.dart b/packages/flow_ui/example/lib/main.dart similarity index 99% rename from example/lib/main.dart rename to packages/flow_ui/example/lib/main.dart index 47e340c..e507399 100644 --- a/example/lib/main.dart +++ b/packages/flow_ui/example/lib/main.dart @@ -4,8 +4,8 @@ import 'package:flow_ui/flow_ui.dart'; import 'package:flutter/services.dart'; import 'package:material_ui/material_ui.dart'; -// The API key lives in env.g.dart — paste yours there. -import 'env.g.dart'; +// The API key arrives through --dart-define (see env.dart). +import 'env.dart'; import 'gemini_api.dart'; void main() { diff --git a/example/linux/.gitignore b/packages/flow_ui/example/linux/.gitignore similarity index 100% rename from example/linux/.gitignore rename to packages/flow_ui/example/linux/.gitignore diff --git a/example/linux/CMakeLists.txt b/packages/flow_ui/example/linux/CMakeLists.txt similarity index 100% rename from example/linux/CMakeLists.txt rename to packages/flow_ui/example/linux/CMakeLists.txt diff --git a/example/linux/flutter/CMakeLists.txt b/packages/flow_ui/example/linux/flutter/CMakeLists.txt similarity index 100% rename from example/linux/flutter/CMakeLists.txt rename to packages/flow_ui/example/linux/flutter/CMakeLists.txt diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/packages/flow_ui/example/linux/flutter/generated_plugin_registrant.cc similarity index 100% rename from example/linux/flutter/generated_plugin_registrant.cc rename to packages/flow_ui/example/linux/flutter/generated_plugin_registrant.cc diff --git a/example/linux/flutter/generated_plugin_registrant.h b/packages/flow_ui/example/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from example/linux/flutter/generated_plugin_registrant.h rename to packages/flow_ui/example/linux/flutter/generated_plugin_registrant.h diff --git a/example/linux/flutter/generated_plugins.cmake b/packages/flow_ui/example/linux/flutter/generated_plugins.cmake similarity index 100% rename from example/linux/flutter/generated_plugins.cmake rename to packages/flow_ui/example/linux/flutter/generated_plugins.cmake diff --git a/example/linux/runner/CMakeLists.txt b/packages/flow_ui/example/linux/runner/CMakeLists.txt similarity index 100% rename from example/linux/runner/CMakeLists.txt rename to packages/flow_ui/example/linux/runner/CMakeLists.txt diff --git a/example/linux/runner/main.cc b/packages/flow_ui/example/linux/runner/main.cc similarity index 100% rename from example/linux/runner/main.cc rename to packages/flow_ui/example/linux/runner/main.cc diff --git a/example/linux/runner/my_application.cc b/packages/flow_ui/example/linux/runner/my_application.cc similarity index 100% rename from example/linux/runner/my_application.cc rename to packages/flow_ui/example/linux/runner/my_application.cc diff --git a/example/linux/runner/my_application.h b/packages/flow_ui/example/linux/runner/my_application.h similarity index 100% rename from example/linux/runner/my_application.h rename to packages/flow_ui/example/linux/runner/my_application.h diff --git a/example/macos/.gitignore b/packages/flow_ui/example/macos/.gitignore similarity index 100% rename from example/macos/.gitignore rename to packages/flow_ui/example/macos/.gitignore diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/flow_ui/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from example/macos/Flutter/Flutter-Debug.xcconfig rename to packages/flow_ui/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/packages/flow_ui/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from example/macos/Flutter/Flutter-Release.xcconfig rename to packages/flow_ui/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/flow_ui/example/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 100% rename from example/macos/Flutter/GeneratedPluginRegistrant.swift rename to packages/flow_ui/example/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/packages/flow_ui/example/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from example/macos/Runner.xcodeproj/project.pbxproj rename to packages/flow_ui/example/macos/Runner.xcodeproj/project.pbxproj diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flow_ui/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flow_ui/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flow_ui/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/flow_ui/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/flow_ui/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/flow_ui/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flow_ui/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/flow_ui/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/example/macos/Runner/AppDelegate.swift b/packages/flow_ui/example/macos/Runner/AppDelegate.swift similarity index 100% rename from example/macos/Runner/AppDelegate.swift rename to packages/flow_ui/example/macos/Runner/AppDelegate.swift diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/flow_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/flow_ui/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from example/macos/Runner/Base.lproj/MainMenu.xib rename to packages/flow_ui/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/flow_ui/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from example/macos/Runner/Configs/AppInfo.xcconfig rename to packages/flow_ui/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/example/macos/Runner/Configs/Debug.xcconfig b/packages/flow_ui/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from example/macos/Runner/Configs/Debug.xcconfig rename to packages/flow_ui/example/macos/Runner/Configs/Debug.xcconfig diff --git a/example/macos/Runner/Configs/Release.xcconfig b/packages/flow_ui/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from example/macos/Runner/Configs/Release.xcconfig rename to packages/flow_ui/example/macos/Runner/Configs/Release.xcconfig diff --git a/example/macos/Runner/Configs/Warnings.xcconfig b/packages/flow_ui/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from example/macos/Runner/Configs/Warnings.xcconfig rename to packages/flow_ui/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/example/macos/Runner/DebugProfile.entitlements b/packages/flow_ui/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from example/macos/Runner/DebugProfile.entitlements rename to packages/flow_ui/example/macos/Runner/DebugProfile.entitlements diff --git a/example/macos/Runner/Info.plist b/packages/flow_ui/example/macos/Runner/Info.plist similarity index 100% rename from example/macos/Runner/Info.plist rename to packages/flow_ui/example/macos/Runner/Info.plist diff --git a/example/macos/Runner/MainFlutterWindow.swift b/packages/flow_ui/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from example/macos/Runner/MainFlutterWindow.swift rename to packages/flow_ui/example/macos/Runner/MainFlutterWindow.swift diff --git a/example/macos/Runner/Release.entitlements b/packages/flow_ui/example/macos/Runner/Release.entitlements similarity index 100% rename from example/macos/Runner/Release.entitlements rename to packages/flow_ui/example/macos/Runner/Release.entitlements diff --git a/example/macos/RunnerTests/RunnerTests.swift b/packages/flow_ui/example/macos/RunnerTests/RunnerTests.swift similarity index 100% rename from example/macos/RunnerTests/RunnerTests.swift rename to packages/flow_ui/example/macos/RunnerTests/RunnerTests.swift diff --git a/example/pubspec.yaml b/packages/flow_ui/example/pubspec.yaml similarity index 89% rename from example/pubspec.yaml rename to packages/flow_ui/example/pubspec.yaml index d1599eb..9dae1e0 100644 --- a/example/pubspec.yaml +++ b/packages/flow_ui/example/pubspec.yaml @@ -7,12 +7,13 @@ environment: sdk: ^3.12.2 flutter: ">=3.44.0" +resolution: workspace + dependencies: flutter: sdk: flutter - flow_ui: - path: ../ + flow_ui: ^0.4.0 material_ui: ^1.0.1 http: ^1.2.0 diff --git a/example/web/favicon.png b/packages/flow_ui/example/web/favicon.png similarity index 100% rename from example/web/favicon.png rename to packages/flow_ui/example/web/favicon.png diff --git a/example/web/icons/Icon-192.png b/packages/flow_ui/example/web/icons/Icon-192.png similarity index 100% rename from example/web/icons/Icon-192.png rename to packages/flow_ui/example/web/icons/Icon-192.png diff --git a/example/web/icons/Icon-512.png b/packages/flow_ui/example/web/icons/Icon-512.png similarity index 100% rename from example/web/icons/Icon-512.png rename to packages/flow_ui/example/web/icons/Icon-512.png diff --git a/example/web/icons/Icon-maskable-192.png b/packages/flow_ui/example/web/icons/Icon-maskable-192.png similarity index 100% rename from example/web/icons/Icon-maskable-192.png rename to packages/flow_ui/example/web/icons/Icon-maskable-192.png diff --git a/example/web/icons/Icon-maskable-512.png b/packages/flow_ui/example/web/icons/Icon-maskable-512.png similarity index 100% rename from example/web/icons/Icon-maskable-512.png rename to packages/flow_ui/example/web/icons/Icon-maskable-512.png diff --git a/example/web/index.html b/packages/flow_ui/example/web/index.html similarity index 100% rename from example/web/index.html rename to packages/flow_ui/example/web/index.html diff --git a/example/web/manifest.json b/packages/flow_ui/example/web/manifest.json similarity index 100% rename from example/web/manifest.json rename to packages/flow_ui/example/web/manifest.json diff --git a/example/windows/.gitignore b/packages/flow_ui/example/windows/.gitignore similarity index 100% rename from example/windows/.gitignore rename to packages/flow_ui/example/windows/.gitignore diff --git a/example/windows/CMakeLists.txt b/packages/flow_ui/example/windows/CMakeLists.txt similarity index 100% rename from example/windows/CMakeLists.txt rename to packages/flow_ui/example/windows/CMakeLists.txt diff --git a/example/windows/flutter/CMakeLists.txt b/packages/flow_ui/example/windows/flutter/CMakeLists.txt similarity index 100% rename from example/windows/flutter/CMakeLists.txt rename to packages/flow_ui/example/windows/flutter/CMakeLists.txt diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/packages/flow_ui/example/windows/flutter/generated_plugin_registrant.cc similarity index 100% rename from example/windows/flutter/generated_plugin_registrant.cc rename to packages/flow_ui/example/windows/flutter/generated_plugin_registrant.cc diff --git a/example/windows/flutter/generated_plugin_registrant.h b/packages/flow_ui/example/windows/flutter/generated_plugin_registrant.h similarity index 100% rename from example/windows/flutter/generated_plugin_registrant.h rename to packages/flow_ui/example/windows/flutter/generated_plugin_registrant.h diff --git a/example/windows/flutter/generated_plugins.cmake b/packages/flow_ui/example/windows/flutter/generated_plugins.cmake similarity index 100% rename from example/windows/flutter/generated_plugins.cmake rename to packages/flow_ui/example/windows/flutter/generated_plugins.cmake diff --git a/example/windows/runner/CMakeLists.txt b/packages/flow_ui/example/windows/runner/CMakeLists.txt similarity index 100% rename from example/windows/runner/CMakeLists.txt rename to packages/flow_ui/example/windows/runner/CMakeLists.txt diff --git a/example/windows/runner/Runner.rc b/packages/flow_ui/example/windows/runner/Runner.rc similarity index 100% rename from example/windows/runner/Runner.rc rename to packages/flow_ui/example/windows/runner/Runner.rc diff --git a/example/windows/runner/flutter_window.cpp b/packages/flow_ui/example/windows/runner/flutter_window.cpp similarity index 100% rename from example/windows/runner/flutter_window.cpp rename to packages/flow_ui/example/windows/runner/flutter_window.cpp diff --git a/example/windows/runner/flutter_window.h b/packages/flow_ui/example/windows/runner/flutter_window.h similarity index 100% rename from example/windows/runner/flutter_window.h rename to packages/flow_ui/example/windows/runner/flutter_window.h diff --git a/example/windows/runner/main.cpp b/packages/flow_ui/example/windows/runner/main.cpp similarity index 100% rename from example/windows/runner/main.cpp rename to packages/flow_ui/example/windows/runner/main.cpp diff --git a/example/windows/runner/resource.h b/packages/flow_ui/example/windows/runner/resource.h similarity index 100% rename from example/windows/runner/resource.h rename to packages/flow_ui/example/windows/runner/resource.h diff --git a/example/windows/runner/resources/app_icon.ico b/packages/flow_ui/example/windows/runner/resources/app_icon.ico similarity index 100% rename from example/windows/runner/resources/app_icon.ico rename to packages/flow_ui/example/windows/runner/resources/app_icon.ico diff --git a/example/windows/runner/runner.exe.manifest b/packages/flow_ui/example/windows/runner/runner.exe.manifest similarity index 100% rename from example/windows/runner/runner.exe.manifest rename to packages/flow_ui/example/windows/runner/runner.exe.manifest diff --git a/example/windows/runner/utils.cpp b/packages/flow_ui/example/windows/runner/utils.cpp similarity index 100% rename from example/windows/runner/utils.cpp rename to packages/flow_ui/example/windows/runner/utils.cpp diff --git a/example/windows/runner/utils.h b/packages/flow_ui/example/windows/runner/utils.h similarity index 100% rename from example/windows/runner/utils.h rename to packages/flow_ui/example/windows/runner/utils.h diff --git a/example/windows/runner/win32_window.cpp b/packages/flow_ui/example/windows/runner/win32_window.cpp similarity index 100% rename from example/windows/runner/win32_window.cpp rename to packages/flow_ui/example/windows/runner/win32_window.cpp diff --git a/example/windows/runner/win32_window.h b/packages/flow_ui/example/windows/runner/win32_window.h similarity index 100% rename from example/windows/runner/win32_window.h rename to packages/flow_ui/example/windows/runner/win32_window.h diff --git a/lib/flow_ui.dart b/packages/flow_ui/lib/flow_ui.dart similarity index 100% rename from lib/flow_ui.dart rename to packages/flow_ui/lib/flow_ui.dart diff --git a/lib/src/models/flow_attachment.dart b/packages/flow_ui/lib/src/models/flow_attachment.dart similarity index 100% rename from lib/src/models/flow_attachment.dart rename to packages/flow_ui/lib/src/models/flow_attachment.dart diff --git a/lib/src/models/flow_attachment_options.dart b/packages/flow_ui/lib/src/models/flow_attachment_options.dart similarity index 100% rename from lib/src/models/flow_attachment_options.dart rename to packages/flow_ui/lib/src/models/flow_attachment_options.dart diff --git a/lib/src/models/flow_message_data.dart b/packages/flow_ui/lib/src/models/flow_message_data.dart similarity index 100% rename from lib/src/models/flow_message_data.dart rename to packages/flow_ui/lib/src/models/flow_message_data.dart diff --git a/lib/src/models/flow_message_part.dart b/packages/flow_ui/lib/src/models/flow_message_part.dart similarity index 100% rename from lib/src/models/flow_message_part.dart rename to packages/flow_ui/lib/src/models/flow_message_part.dart diff --git a/lib/src/styles/flow_chat_view_style.dart b/packages/flow_ui/lib/src/styles/flow_chat_view_style.dart similarity index 100% rename from lib/src/styles/flow_chat_view_style.dart rename to packages/flow_ui/lib/src/styles/flow_chat_view_style.dart diff --git a/lib/src/styles/flow_code_block_style.dart b/packages/flow_ui/lib/src/styles/flow_code_block_style.dart similarity index 100% rename from lib/src/styles/flow_code_block_style.dart rename to packages/flow_ui/lib/src/styles/flow_code_block_style.dart diff --git a/lib/src/styles/flow_composer_style.dart b/packages/flow_ui/lib/src/styles/flow_composer_style.dart similarity index 100% rename from lib/src/styles/flow_composer_style.dart rename to packages/flow_ui/lib/src/styles/flow_composer_style.dart diff --git a/lib/src/styles/flow_confirmation_style.dart b/packages/flow_ui/lib/src/styles/flow_confirmation_style.dart similarity index 100% rename from lib/src/styles/flow_confirmation_style.dart rename to packages/flow_ui/lib/src/styles/flow_confirmation_style.dart diff --git a/lib/src/styles/flow_error_state_style.dart b/packages/flow_ui/lib/src/styles/flow_error_state_style.dart similarity index 100% rename from lib/src/styles/flow_error_state_style.dart rename to packages/flow_ui/lib/src/styles/flow_error_state_style.dart diff --git a/lib/src/styles/flow_markdown_style.dart b/packages/flow_ui/lib/src/styles/flow_markdown_style.dart similarity index 100% rename from lib/src/styles/flow_markdown_style.dart rename to packages/flow_ui/lib/src/styles/flow_markdown_style.dart diff --git a/lib/src/styles/flow_menu_style.dart b/packages/flow_ui/lib/src/styles/flow_menu_style.dart similarity index 100% rename from lib/src/styles/flow_menu_style.dart rename to packages/flow_ui/lib/src/styles/flow_menu_style.dart diff --git a/lib/src/styles/flow_message_actions_style.dart b/packages/flow_ui/lib/src/styles/flow_message_actions_style.dart similarity index 100% rename from lib/src/styles/flow_message_actions_style.dart rename to packages/flow_ui/lib/src/styles/flow_message_actions_style.dart diff --git a/lib/src/styles/flow_message_style.dart b/packages/flow_ui/lib/src/styles/flow_message_style.dart similarity index 100% rename from lib/src/styles/flow_message_style.dart rename to packages/flow_ui/lib/src/styles/flow_message_style.dart diff --git a/lib/src/styles/flow_pill_style.dart b/packages/flow_ui/lib/src/styles/flow_pill_style.dart similarity index 100% rename from lib/src/styles/flow_pill_style.dart rename to packages/flow_ui/lib/src/styles/flow_pill_style.dart diff --git a/lib/src/styles/flow_suggestion_style.dart b/packages/flow_ui/lib/src/styles/flow_suggestion_style.dart similarity index 100% rename from lib/src/styles/flow_suggestion_style.dart rename to packages/flow_ui/lib/src/styles/flow_suggestion_style.dart diff --git a/lib/src/styles/flow_thread_list_style.dart b/packages/flow_ui/lib/src/styles/flow_thread_list_style.dart similarity index 100% rename from lib/src/styles/flow_thread_list_style.dart rename to packages/flow_ui/lib/src/styles/flow_thread_list_style.dart diff --git a/lib/src/styles/flow_toast_style.dart b/packages/flow_ui/lib/src/styles/flow_toast_style.dart similarity index 100% rename from lib/src/styles/flow_toast_style.dart rename to packages/flow_ui/lib/src/styles/flow_toast_style.dart diff --git a/lib/src/styles/flow_tool_style.dart b/packages/flow_ui/lib/src/styles/flow_tool_style.dart similarity index 100% rename from lib/src/styles/flow_tool_style.dart rename to packages/flow_ui/lib/src/styles/flow_tool_style.dart diff --git a/lib/src/theme/flow_colors.dart b/packages/flow_ui/lib/src/theme/flow_colors.dart similarity index 100% rename from lib/src/theme/flow_colors.dart rename to packages/flow_ui/lib/src/theme/flow_colors.dart diff --git a/lib/src/theme/flow_syntax_colors.dart b/packages/flow_ui/lib/src/theme/flow_syntax_colors.dart similarity index 100% rename from lib/src/theme/flow_syntax_colors.dart rename to packages/flow_ui/lib/src/theme/flow_syntax_colors.dart diff --git a/lib/src/theme/flow_theme.dart b/packages/flow_ui/lib/src/theme/flow_theme.dart similarity index 100% rename from lib/src/theme/flow_theme.dart rename to packages/flow_ui/lib/src/theme/flow_theme.dart diff --git a/lib/src/theme/flow_typography.dart b/packages/flow_ui/lib/src/theme/flow_typography.dart similarity index 100% rename from lib/src/theme/flow_typography.dart rename to packages/flow_ui/lib/src/theme/flow_typography.dart diff --git a/lib/src/utils/flow_asterisk_painter.dart b/packages/flow_ui/lib/src/utils/flow_asterisk_painter.dart similarity index 100% rename from lib/src/utils/flow_asterisk_painter.dart rename to packages/flow_ui/lib/src/utils/flow_asterisk_painter.dart diff --git a/lib/src/utils/flow_attachment_error.dart b/packages/flow_ui/lib/src/utils/flow_attachment_error.dart similarity index 100% rename from lib/src/utils/flow_attachment_error.dart rename to packages/flow_ui/lib/src/utils/flow_attachment_error.dart diff --git a/lib/src/utils/flow_attachment_intake.dart b/packages/flow_ui/lib/src/utils/flow_attachment_intake.dart similarity index 100% rename from lib/src/utils/flow_attachment_intake.dart rename to packages/flow_ui/lib/src/utils/flow_attachment_intake.dart diff --git a/lib/src/utils/flow_chip_text.dart b/packages/flow_ui/lib/src/utils/flow_chip_text.dart similarity index 100% rename from lib/src/utils/flow_chip_text.dart rename to packages/flow_ui/lib/src/utils/flow_chip_text.dart diff --git a/lib/src/utils/flow_circle_button.dart b/packages/flow_ui/lib/src/utils/flow_circle_button.dart similarity index 100% rename from lib/src/utils/flow_circle_button.dart rename to packages/flow_ui/lib/src/utils/flow_circle_button.dart diff --git a/lib/src/utils/flow_clipboard_paste.dart b/packages/flow_ui/lib/src/utils/flow_clipboard_paste.dart similarity index 100% rename from lib/src/utils/flow_clipboard_paste.dart rename to packages/flow_ui/lib/src/utils/flow_clipboard_paste.dart diff --git a/lib/src/utils/flow_clipboard_paste_stub.dart b/packages/flow_ui/lib/src/utils/flow_clipboard_paste_stub.dart similarity index 100% rename from lib/src/utils/flow_clipboard_paste_stub.dart rename to packages/flow_ui/lib/src/utils/flow_clipboard_paste_stub.dart diff --git a/lib/src/utils/flow_clipboard_paste_web.dart b/packages/flow_ui/lib/src/utils/flow_clipboard_paste_web.dart similarity index 100% rename from lib/src/utils/flow_clipboard_paste_web.dart rename to packages/flow_ui/lib/src/utils/flow_clipboard_paste_web.dart diff --git a/lib/src/utils/flow_file_drop.dart b/packages/flow_ui/lib/src/utils/flow_file_drop.dart similarity index 100% rename from lib/src/utils/flow_file_drop.dart rename to packages/flow_ui/lib/src/utils/flow_file_drop.dart diff --git a/lib/src/utils/flow_file_drop_stub.dart b/packages/flow_ui/lib/src/utils/flow_file_drop_stub.dart similarity index 100% rename from lib/src/utils/flow_file_drop_stub.dart rename to packages/flow_ui/lib/src/utils/flow_file_drop_stub.dart diff --git a/lib/src/utils/flow_file_drop_web.dart b/packages/flow_ui/lib/src/utils/flow_file_drop_web.dart similarity index 100% rename from lib/src/utils/flow_file_drop_web.dart rename to packages/flow_ui/lib/src/utils/flow_file_drop_web.dart diff --git a/lib/src/utils/flow_file_picker.dart b/packages/flow_ui/lib/src/utils/flow_file_picker.dart similarity index 100% rename from lib/src/utils/flow_file_picker.dart rename to packages/flow_ui/lib/src/utils/flow_file_picker.dart diff --git a/lib/src/utils/flow_gradient_outline.dart b/packages/flow_ui/lib/src/utils/flow_gradient_outline.dart similarity index 100% rename from lib/src/utils/flow_gradient_outline.dart rename to packages/flow_ui/lib/src/utils/flow_gradient_outline.dart diff --git a/lib/src/utils/flow_markdown_parser.dart b/packages/flow_ui/lib/src/utils/flow_markdown_parser.dart similarity index 100% rename from lib/src/utils/flow_markdown_parser.dart rename to packages/flow_ui/lib/src/utils/flow_markdown_parser.dart diff --git a/lib/src/utils/flow_menu_core.dart b/packages/flow_ui/lib/src/utils/flow_menu_core.dart similarity index 100% rename from lib/src/utils/flow_menu_core.dart rename to packages/flow_ui/lib/src/utils/flow_menu_core.dart diff --git a/lib/src/utils/flow_menu_sheet.dart b/packages/flow_ui/lib/src/utils/flow_menu_sheet.dart similarity index 100% rename from lib/src/utils/flow_menu_sheet.dart rename to packages/flow_ui/lib/src/utils/flow_menu_sheet.dart diff --git a/lib/src/utils/flow_reveal_engine.dart b/packages/flow_ui/lib/src/utils/flow_reveal_engine.dart similarity index 100% rename from lib/src/utils/flow_reveal_engine.dart rename to packages/flow_ui/lib/src/utils/flow_reveal_engine.dart diff --git a/lib/src/utils/flow_selection.dart b/packages/flow_ui/lib/src/utils/flow_selection.dart similarity index 100% rename from lib/src/utils/flow_selection.dart rename to packages/flow_ui/lib/src/utils/flow_selection.dart diff --git a/lib/src/utils/flow_shimmer_sweep.dart b/packages/flow_ui/lib/src/utils/flow_shimmer_sweep.dart similarity index 100% rename from lib/src/utils/flow_shimmer_sweep.dart rename to packages/flow_ui/lib/src/utils/flow_shimmer_sweep.dart diff --git a/lib/src/utils/flow_syntax_highlighter.dart b/packages/flow_ui/lib/src/utils/flow_syntax_highlighter.dart similarity index 100% rename from lib/src/utils/flow_syntax_highlighter.dart rename to packages/flow_ui/lib/src/utils/flow_syntax_highlighter.dart diff --git a/lib/src/utils/flow_toast_layer.dart b/packages/flow_ui/lib/src/utils/flow_toast_layer.dart similarity index 100% rename from lib/src/utils/flow_toast_layer.dart rename to packages/flow_ui/lib/src/utils/flow_toast_layer.dart diff --git a/lib/src/utils/flow_web_dom.dart b/packages/flow_ui/lib/src/utils/flow_web_dom.dart similarity index 100% rename from lib/src/utils/flow_web_dom.dart rename to packages/flow_ui/lib/src/utils/flow_web_dom.dart diff --git a/lib/src/widgets/flow_attachment_group.dart b/packages/flow_ui/lib/src/widgets/flow_attachment_group.dart similarity index 100% rename from lib/src/widgets/flow_attachment_group.dart rename to packages/flow_ui/lib/src/widgets/flow_attachment_group.dart diff --git a/lib/src/widgets/flow_attachment_preview.dart b/packages/flow_ui/lib/src/widgets/flow_attachment_preview.dart similarity index 100% rename from lib/src/widgets/flow_attachment_preview.dart rename to packages/flow_ui/lib/src/widgets/flow_attachment_preview.dart diff --git a/lib/src/widgets/flow_chat_view.dart b/packages/flow_ui/lib/src/widgets/flow_chat_view.dart similarity index 100% rename from lib/src/widgets/flow_chat_view.dart rename to packages/flow_ui/lib/src/widgets/flow_chat_view.dart diff --git a/lib/src/widgets/flow_code_block.dart b/packages/flow_ui/lib/src/widgets/flow_code_block.dart similarity index 100% rename from lib/src/widgets/flow_code_block.dart rename to packages/flow_ui/lib/src/widgets/flow_code_block.dart diff --git a/lib/src/widgets/flow_composer.dart b/packages/flow_ui/lib/src/widgets/flow_composer.dart similarity index 100% rename from lib/src/widgets/flow_composer.dart rename to packages/flow_ui/lib/src/widgets/flow_composer.dart diff --git a/lib/src/widgets/flow_confirmation.dart b/packages/flow_ui/lib/src/widgets/flow_confirmation.dart similarity index 100% rename from lib/src/widgets/flow_confirmation.dart rename to packages/flow_ui/lib/src/widgets/flow_confirmation.dart diff --git a/lib/src/widgets/flow_drop_target.dart b/packages/flow_ui/lib/src/widgets/flow_drop_target.dart similarity index 100% rename from lib/src/widgets/flow_drop_target.dart rename to packages/flow_ui/lib/src/widgets/flow_drop_target.dart diff --git a/lib/src/widgets/flow_error_state.dart b/packages/flow_ui/lib/src/widgets/flow_error_state.dart similarity index 100% rename from lib/src/widgets/flow_error_state.dart rename to packages/flow_ui/lib/src/widgets/flow_error_state.dart diff --git a/lib/src/widgets/flow_greeting.dart b/packages/flow_ui/lib/src/widgets/flow_greeting.dart similarity index 100% rename from lib/src/widgets/flow_greeting.dart rename to packages/flow_ui/lib/src/widgets/flow_greeting.dart diff --git a/lib/src/widgets/flow_markdown.dart b/packages/flow_ui/lib/src/widgets/flow_markdown.dart similarity index 100% rename from lib/src/widgets/flow_markdown.dart rename to packages/flow_ui/lib/src/widgets/flow_markdown.dart diff --git a/lib/src/widgets/flow_menu.dart b/packages/flow_ui/lib/src/widgets/flow_menu.dart similarity index 100% rename from lib/src/widgets/flow_menu.dart rename to packages/flow_ui/lib/src/widgets/flow_menu.dart diff --git a/lib/src/widgets/flow_message.dart b/packages/flow_ui/lib/src/widgets/flow_message.dart similarity index 100% rename from lib/src/widgets/flow_message.dart rename to packages/flow_ui/lib/src/widgets/flow_message.dart diff --git a/lib/src/widgets/flow_message_actions.dart b/packages/flow_ui/lib/src/widgets/flow_message_actions.dart similarity index 100% rename from lib/src/widgets/flow_message_actions.dart rename to packages/flow_ui/lib/src/widgets/flow_message_actions.dart diff --git a/lib/src/widgets/flow_model_selector.dart b/packages/flow_ui/lib/src/widgets/flow_model_selector.dart similarity index 100% rename from lib/src/widgets/flow_model_selector.dart rename to packages/flow_ui/lib/src/widgets/flow_model_selector.dart diff --git a/lib/src/widgets/flow_pill.dart b/packages/flow_ui/lib/src/widgets/flow_pill.dart similarity index 100% rename from lib/src/widgets/flow_pill.dart rename to packages/flow_ui/lib/src/widgets/flow_pill.dart diff --git a/lib/src/widgets/flow_shimmer_text.dart b/packages/flow_ui/lib/src/widgets/flow_shimmer_text.dart similarity index 100% rename from lib/src/widgets/flow_shimmer_text.dart rename to packages/flow_ui/lib/src/widgets/flow_shimmer_text.dart diff --git a/lib/src/widgets/flow_streaming_text.dart b/packages/flow_ui/lib/src/widgets/flow_streaming_text.dart similarity index 100% rename from lib/src/widgets/flow_streaming_text.dart rename to packages/flow_ui/lib/src/widgets/flow_streaming_text.dart diff --git a/lib/src/widgets/flow_suggestion.dart b/packages/flow_ui/lib/src/widgets/flow_suggestion.dart similarity index 100% rename from lib/src/widgets/flow_suggestion.dart rename to packages/flow_ui/lib/src/widgets/flow_suggestion.dart diff --git a/lib/src/widgets/flow_thinking_indicator.dart b/packages/flow_ui/lib/src/widgets/flow_thinking_indicator.dart similarity index 100% rename from lib/src/widgets/flow_thinking_indicator.dart rename to packages/flow_ui/lib/src/widgets/flow_thinking_indicator.dart diff --git a/lib/src/widgets/flow_thread.dart b/packages/flow_ui/lib/src/widgets/flow_thread.dart similarity index 100% rename from lib/src/widgets/flow_thread.dart rename to packages/flow_ui/lib/src/widgets/flow_thread.dart diff --git a/lib/src/widgets/flow_thread_list.dart b/packages/flow_ui/lib/src/widgets/flow_thread_list.dart similarity index 100% rename from lib/src/widgets/flow_thread_list.dart rename to packages/flow_ui/lib/src/widgets/flow_thread_list.dart diff --git a/lib/src/widgets/flow_toast.dart b/packages/flow_ui/lib/src/widgets/flow_toast.dart similarity index 100% rename from lib/src/widgets/flow_toast.dart rename to packages/flow_ui/lib/src/widgets/flow_toast.dart diff --git a/lib/src/widgets/flow_tool.dart b/packages/flow_ui/lib/src/widgets/flow_tool.dart similarity index 100% rename from lib/src/widgets/flow_tool.dart rename to packages/flow_ui/lib/src/widgets/flow_tool.dart diff --git a/packages/flow_ui/pubspec.yaml b/packages/flow_ui/pubspec.yaml new file mode 100644 index 0000000..44fd76a --- /dev/null +++ b/packages/flow_ui/pubspec.yaml @@ -0,0 +1,42 @@ +name: flow_ui +description: Flow UI is an open-source Flutter UI library to build production-grade Chat & AI assistant interfaces. +version: 0.4.0 +homepage: https://flowui.stac.dev/ +repository: https://github.com/StacDev/flow_ui/tree/main/packages/flow_ui +issue_tracker: https://github.com/StacDev/flow_ui/issues +documentation: https://flowui.stac.dev/ +topics: + - ai + - chat + - chatbot + - assistant + - ui + +environment: + sdk: ^3.12.2 + flutter: ">=3.44.0" + +resolution: workspace + +dependencies: + flutter: + sdk: flutter + # Google Sans and Google Sans Code, fetched from Google Fonts at runtime — + # the one dependency outside flutter.dev's packages; no font files ship. + google_fonts: ^8.2.1 + material_ui: ^1.0.0 + # The composer's built-in attach button opens this. flutter.dev-published + # and endorsed on all six platforms, and — unlike image_picker — it adds + # no permission, no FileProvider and no Play-services entry to a host's + # Android manifest, and no Info.plist strings to a host's iOS build. The + # one thing a host must do is a macOS entitlement + # (com.apple.security.files.user-selected.read-only); without it the + # dialog opens onto nothing. Hosts that never wire an attach callback + # ship this and configure nothing. + file_selector: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + diff --git a/packages/stacflow/.pubignore b/packages/stacflow/.pubignore new file mode 100644 index 0000000..cedf0d5 --- /dev/null +++ b/packages/stacflow/.pubignore @@ -0,0 +1,25 @@ +# Replaces .gitignore for publishing. +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ +*.iml +*.ipr +*.iws +.idea/ +.env +.metadata +pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins-dependencies +build/ +coverage/ +doc/ +tool/ diff --git a/packages/stacflow/CHANGELOG.md b/packages/stacflow/CHANGELOG.md new file mode 100644 index 0000000..59e5c75 --- /dev/null +++ b/packages/stacflow/CHANGELOG.md @@ -0,0 +1,7 @@ +## 0.1.0 + +- First release: `StacFlowChat`, the chat controller, and `StacFlowChatView`, the ready-made screen built on flow_ui, with streaming, stop, retry, regenerate, edit-and-resend, image attachments, copy, feedback and a model selector. +- `GeminiProvider`, `OpenAIProvider` (Chat Completions, with `baseUrl` for Ollama, Groq, OpenRouter, LM Studio and vLLM) and `AnthropicProvider`, each streaming replies and taking image input, all called with the developer's own key. +- `AgentConfig` for instructions and sampling, `FlowStrings` for every string the SDK shows, `ChatError` for failed turns. +- Tools: `Tool` registered with `StacFlowChat(tools:)`, run by the pure-Dart `runToolLoop` with results fed back to the model; permission classes `read`, `write` and `destructive`, the confirmation card for gated calls, a tool card per call, `toolBodyBuilder` on the view, `ChatState.toolCalls`, `respondToToolCall`, `maxToolRounds` and the new `FlowStrings` for approvals and tool failures. Calls and results are replayed to the model in later turns. +- Adapters emit the wire `SseEvent` union, tool calls included; `TurnTransport`, `TurnRequest` and the wire types are public for custom providers. diff --git a/packages/stacflow/LICENSE b/packages/stacflow/LICENSE new file mode 100644 index 0000000..ee6ff7f --- /dev/null +++ b/packages/stacflow/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Stac + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/stacflow/README.md b/packages/stacflow/README.md new file mode 100644 index 0000000..6a071f6 --- /dev/null +++ b/packages/stacflow/README.md @@ -0,0 +1,201 @@ +

+ Flow UI logo +

+ +

stacflow

+ +

+ 🎨 flow_ui + · 🧩 Playground +

+ +

+Streaming AI chat for Flutter, built on flow_ui, talking to Gemini, OpenAI or Claude with your own API key. +

+ +> [!IMPORTANT] +> stacflow is pre-1.0. The API is still settling, and minor releases may +> carry breaking changes: pin a minor version and read the changelog when +> upgrading. + +## Quickstart + +```yaml +dependencies: + stacflow: ^0.1.0 +``` + +```dart +import 'package:material_ui/material_ui.dart'; +import 'package:stacflow/stacflow.dart'; + +void main() => runApp(const App()); + +class App extends StatelessWidget { + const App({super.key}); + + @override + Widget build(BuildContext context) => MaterialApp( + theme: ThemeData(extensions: [FlowTheme.light()]), + darkTheme: ThemeData( + brightness: Brightness.dark, + extensions: [FlowTheme.dark()], + ), + home: const ChatScreen(), + ); +} + +class ChatScreen extends StatefulWidget { + const ChatScreen({super.key}); + + @override + State createState() => _ChatScreenState(); +} + +class _ChatScreenState extends State { + late final chat = StacFlowChat( + provider: GeminiProvider( + apiKey: const String.fromEnvironment('GEMINI_API_KEY'), + ), + ); + + @override + void dispose() { + chat.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => + Scaffold(body: StacFlowChatView(chat: chat)); +} +``` + +```bash +flutter run --dart-define=GEMINI_API_KEY=AIza... +``` + +That is the whole integration. The view streams replies with a thinking +indicator, renders markdown and code, stops mid-reply, retries failed +turns, regenerates, takes image attachments from the picker, a drop or a +paste, copies replies, records feedback, offers a model selector when +the provider lists more than one model, and runs the tools you register, +asking first when a tool is destructive. One import brings flow_ui along, +so every widget it exports is available too. + +## Example + +[`example/`](https://github.com/StacDev/flow_ui/tree/main/packages/stacflow/example) +is this screen, runnable against Gemini: + +```bash +cd example && flutter run --dart-define=GEMINI_API_KEY=AIza... +``` + +## Providers + +| Provider | Class | Default model | Key | +|---|---|---|---| +| Gemini | `GeminiProvider` | `gemini-3.6-flash` | An AI Studio API key | +| OpenAI, and any compatible server | `OpenAIProvider` | `gpt-5-mini` | An OpenAI API key; set `baseUrl` for Ollama, Groq, OpenRouter, LM Studio or vLLM | +| Claude | `AnthropicProvider` | `claude-opus-5` | An Anthropic API key; `claude-sonnet-5` is the cheaper model | + +Every provider takes `model`, `baseUrl`, extra `headers`, an `httpClient`, +the `models` a selector should offer, and first-byte and idle timeouts. +Instructions and sampling live on the chat: + +```dart +StacFlowChat( + provider: AnthropicProvider( + apiKey: key, + models: const [ + ModelOption(id: 'claude-opus-5', label: 'Claude Opus 5'), + ModelOption(id: 'claude-sonnet-5', label: 'Claude Sonnet 5'), + ], + ), + agent: const AgentConfig(instructions: 'You are a concise assistant.'), +) +``` + +## Tools + +A tool is an action the app lets the model take. Register it on the chat +with a name, a description the model reads, a JSON Schema for its +arguments and the handler that runs it: + +```dart +StacFlowChat( + provider: GeminiProvider(apiKey: key), + tools: [ + Tool( + name: 'set_theme', + description: 'Switches the app between light and dark mode.', + parameters: const { + 'type': 'object', + 'properties': { + 'mode': {'type': 'string', 'enum': ['light', 'dark', 'system']}, + }, + 'required': ['mode'], + }, + label: 'Set theme', + detail: (args) => args['mode'] as String?, + run: (call) { + themeMode.value = ThemeMode.values.byName(call.args['mode'] as String); + return {'mode': call.args['mode']}; + }, + ), + ], +) +``` + +When the model calls it, the thread shows a tool card that moves from +pending to complete, with the arguments and the result behind a +disclosure, and the model finishes its reply with the result in hand. A +handler returns any JSON-encodable value; throw a `ToolException` to fail +in your own words, and check `call.isAborted` in long handlers, since a +stop, a timeout or the end of the turn cancels the call. + +Every tool carries a permission: `read` runs silently, `write` (the +default) runs unless the tool sets `confirm: true`, and `destructive` +always shows a confirmation card first. The card's question comes from +`confirmation`, or "Allow