Conversation
Read-only visualization of a role's effective service+component access. Renders allowed services with verbs and a collapsible "denied" list. Foundation for the AI admin UI: surfaces "what can the AI see" — same panel will be embedded in the AI Chat service form so admins can read the scope before assigning a role to an AI. Adds /api-connections/role-based-access/:id/scope.
Pre-commit hook formatted files in the previous commit but the writes weren't re-staged. Catching them up here.
Use a local const + explicit null/undefined guard so TS narrows the type before passing roleId into the role service.
- Adds "View scope" row action on the roles list (eye icon). - Adds "View scope" button on the role detail page in edit mode. - Adds an ng-content slot in df-role-scope's error block so pages can project a recovery link; standalone scope page projects "Back to roles list" so an invalid id doesn't dead-end.
Keys like 'roleScope.heading' were rendering as raw text because DF's transloco scope provider exposes scoped keys as 'roles.<key>'. Updated every reference (component, page wrapper, role detail button, list action) to use the 'roles.' prefix. Bumped font sizes, padding, gaps and used dark-mode-friendly colors so the panel visually matches the rest of the DF admin UI rather than looking like a tiny widget.
The single /ai route was filtering services to the MCP group only.
Splitting into three sub-routes so connections, chat services, and MCP
servers each get their own list/create/edit UI without inventing
new components — they all reuse ServiceRoutes filtered by group.
/ai
/connections -> service-type group "AI" (ai_connection)
/chat-services-> service-type group "AI Chat" (ai_chat)
/mcp -> service-type group "MCP" (mcp)
Sidebar picks up the children automatically via transformRoutes.
Adds nav i18n keys + extends the always-allowed route list so
non-admin tabs that allow ROUTES.AI inherit the children.
Empty /ai redirects to /ai/connections as the landing tab.
When a service of type ai_chat is being created/edited and ai_role_id is set, render the role-scope panel inside the config expansion panel. The panel reacts to ai_role_id changes via @Input/ngOnChanges so an admin picking different roles sees the AI's effective data scope update inline — answering "what will this AI be able to see?" before the service is saved. Visible only when type === 'ai_chat'; the same form template renders every non-network/non-script service type, so the panel is gated to avoid showing on all other services.
New module under src/app/adf-ai-chat/ providing the in-browser chat
experience for ai_chat services:
- df-ai-chat (main): service picker (when >1 chat service exists),
session sidebar, message feed, polling-based "thinking" updates,
data-scope display in the header.
- df-chat-session-list: session sidebar with new-chat button.
- df-chat-message: per-message render dispatching by role
(user / assistant / tool / system) with bubble styling and
optional usage stats.
- df-chat-tool-result: collapsible card for tool execution results,
pretty-prints JSON, flags errors.
- df-chat-input: textarea with send button + Enter-to-send.
- ai-chat.service: HTTP client for /api/v2/{service}/session*.
Send-message flow: optimistic user-bubble append, 1s polling against
GET /session/{id} while awaiting the assistant response so tool calls
appear as the agentic loop produces them; final reconciliation on
completion. SSE streaming is intentionally a fast-follow.
Routes: /ai/chat and /ai/chat/:sessionId both point at the same
component; sessionId param triggers an open-on-load.
…sations"
The role-scope panel embedded in the chat-service form was reading
getConfigControl('ai_role_id').value at template-init time — but that
control doesn't exist until the user picks a Service Type and the
config FormGroup is built. The null deref threw an error during change
detection, leaving the Service Type dropdown stuck open and the form
unable to populate fields.
Adds an aiRoleId getter that null-checks the form path and returns a
number-or-null so the template can use it without optional chaining
(which Angular's strict template type-check warns on because the cast
in getConfigControl claims non-null).
Also rename "Chat" → "Conversations" in the AI sub-nav so the user
doesn't see "Chat Services" and "Chat" as visually-paired siblings;
"Chat Services" is the admin config and "Conversations" is the
in-browser chat experience.
When creating an AI Chat service, the form needs an integer ai_service_id and ai_role_id — admins were guessing what to type. The prereqs panel appears above the config when type === 'ai_chat' and: - Lists existing AI Connections with their IDs (copy/paste source). - Lists existing Roles with their IDs and a "view scope" link to the read-only role-scope page. - Flags missing prerequisites in red with a "Create one now" button. - Links to /ai/connections/create and /api-connections/role-based-access/create. Removes the "stare at an empty integer field and guess" UX.
…mart scroll - df-ai-chat-prereqs now writes selections back to the form (selectConnection / selectRole). The form's integer ai_service_id and ai_role_id fields are hidden when type=ai_chat — admins click chips instead of typing IDs. - df-ai-test-connection: stroked button on the AI Connection form that POSTs to /_internal/ai/test-connection with the current config and surfaces pass/fail + model count inline. Lets admins validate provider/key without saving first. - Chat tool-result cards auto-expand when is_error so failures aren't hidden. - Optimistic user-message id uses a monotonic counter (not -Date.now()) so rapid sends can't collide. - Chat scroll: track whether the user is pinned to the bottom; only auto- scroll on new messages when they are. Sending always pins. Opening a session always pins. - Assistant content: split fenced code blocks (```...```) into <pre><code> segments via pure-text binding. No innerHTML, so no XSS surface.
7 new spec files, 56 tests, all green under jest.config.ci.js: - df-role-scope: bitmask decoding (GET=1, all=31, etc.), denied list, '*' wildcard for empty component, error handling, hasRoleId guard, unknown service-id skip. Pure-class tests (no TestBed) so the transloco-pipe template doesn't drag in @ngneat/transloco's ESM. - ai-chat.service: HttpClientTestingModule covering listChatServices, list/get/create sessions, sendMessage, deleteSession URL+method+body. - df-chat-input: canSend gating (busy/disabled/empty/whitespace), Enter-to-send vs Shift+Enter, trimmed emit + field clear. - df-chat-message: segments parser for fenced code blocks, hasToolCalls / hasUsage flags. - df-chat-tool-result: auto-expand-on-error, JSON pretty-print, blank fallback. - df-ai-chat-prereqs: parallel fetch + failure tolerance, selection emits. - df-ai-test-connection: provider-required guard, POST shape, success + error response handling, plain-string model labels. jest.config.js: map `flat` (transloco's ESM dep) and add it to transformIgnorePatterns so role-scope's transloco import resolves under Jest. jest.config.ci.js: add the 7 new specs to the curated testMatch list.
… switch The previous implementation used a single shared pollTimer slot. Two concurrent sends would step on each other: send-2's startPolling would clear send-1's timer, and send-1's finalize would clear send-2's. The chat would silently stop refreshing. Track in-flight sends with a counter: - send increments inFlightSends and starts the timer (idempotent). - finalize decrements; only stops polling when count reaches 0. - selectService and deleteSession force a clean reset (active session going away invalidates any in-progress polling). Adds df-ai-chat.poll.spec.ts covering the three tracked transitions.
Surfaces what the chat module already captures (per-session token counts + tool-call counts + user/role/service IDs) as a usage analytics view. No backend changes — pure client-side aggregation off the existing endpoints. Phase 1 swaps in a backend rollup endpoint when scale demands it. New module under src/app/adf-ai-usage/: - df-ai-usage (main): time-range filter (24h/7d/30d/all), refresh button, summary cards, time-series + grouped bars + cost. - df-usage-summary: 6-card top line (sessions, in/out/total tokens, tool calls, avg/session). - df-usage-stacked-area: hand-rolled SVG stacked-area chart of input vs output tokens by day with grid + axis labels (no chart library). - df-usage-bars: grouped bar chart for top users / roles / services with input/output split, "N of M" overflow indicator, optional click-through (services jump to /ai/chat?service=). - df-cost-estimator: per-provider breakdown using stored token counts multiplied by editable per-1k rates (defaults for anthropic/openai/ xai/ollama/openai_compatible). Inline rate edits recompute live. - usage.service: parallel forkJoin loader — chat services, sessions for each, users + roles + AI Connection providers (for the cost attribution lookup). Aggregation utilities (groupBy, summarize, timeSeries, filterByRange) have full Jest coverage. Cost utilities (estimateCost, formatUSD, DEFAULT_RATES) have full coverage. 25 new tests, 135 total in the CI suite. Sidebar: AI -> Usage. Empty state links straight to /ai/chat to generate data. Anything cost-attribution-shaped is a one-day Phase 1 backend lift on top of this.
The ID badges were misread as "you must know the ID" rather than decorative. Removed them — the chip now shows just the human label and a checkmark on selection. Added an italic hint above the chip list pointing the admin at the click-to-select behavior. Renamed the inline scope link from "scope" to "what can this role see?" so it reads as the question it answers.
…i_chat The previous *ngSwitchCase="true" was comparing the string "true" to a boolean true and never matched, so the integer fields rendered alongside the prereqs panel and the form required users to type IDs. Replaced with a straightforward *ngIf negation. Also bumps the prereqs panel typography (1rem base, 1.2rem header, 0.95rem chips) — the original sizes read as a footer rather than the load-bearing setup step it actually is.
Re-frames the dashboard around the AI gateway story: every AI request
through DreamFactory (REST calls from client apps + the in-DF chat UI)
is logged to ai_usage_log by df-ai. The dashboard now reads that
single source instead of the chat-only ai_chat_sessions table.
Backend call returns totals + by_service / by_user / by_role /
by_provider / by_model / by_resource + daily series. Client-side
hydration adds human-readable user / role / service labels.
UI changes:
- "Sessions" -> "Requests" in the summary
- New "Errors" card (red when > 0) and "Avg latency" card
- New bar charts: by provider, by endpoint (resource type)
- "By chat service" -> "By AI Connection" — connection IS the gateway
- Empty state copy reflects the new framing ("every AI request through
DreamFactory" not "chat sessions")
Old session-based aggregation utilities still exist for now —
unused but not removed. Will be deleted once the pivot stabilizes.
Three usability improvements + one bug fix tying them together:
1. **df-ai-model-picker**: drop-down model selection on the AI Connection
form. Hits POST /_internal/ai/test-connection with the form's current
provider+key+url and lists every model the provider returns. Selecting
writes to config.default_model. Includes a "type custom" toggle for
admins who know the model id but don't want to fetch (offline / model
not yet listed by the provider).
2. **/_internal interceptor fix**: session-token.interceptor only
attached the X-DreamFactory-Session-Token header to /api/* URLs.
That meant Test Connection and the org-wide usage endpoint
silently went unauth'd from the UI ("Admin access required"
error). Extended to /_internal/* too.
3. **Test Connection validation**: provider-specific upfront checks
so admins get "openai_compatible requires a Base URL" instead of
the cURL "No host part in the URL" the provider was bubbling up.
4. **Nav reframe**: routes order Connections first (gateway primary),
then Usage (visibility), then Conversations (built-in playground),
then MCP (data exposure), then Chat Services last (admin-only
setup machinery for the chat UI). Sidebar auto-generates from
route order so this surfaces immediately.
5. **Cost defaults**: openai_compatible defaults to $0/1k since the
common case is a self-hosted endpoint. Admins can override per-row
in the dashboard if modeling GPU amortization. ollama already $0.
Plus the form-bug fix from earlier in the day still applies — ai_chat
form correctly hides ai_service_id/ai_role_id integer fields, and
ai_connection form now hides default_model in favor of the picker.
…, MCP section Rebuild the /ai/usage page to answer team-lead-grade questions about AI spend through DreamFactory. The page is now a single-pane Gateway view that overlays paid AI provider calls and inbound MCP traffic. Performance + theming foundations - Cache derived views in instance fields populated from a single recompute() in the API-response handler, instead of getter-bound template inputs. Getter-bound @inputs handed child components new array/Map references on every change-detection tick, which made ngOnChanges/rebuild loops pin a CPU core and (on weaker machines) killed the browser tab. - Wrap every panel in mat-card so the Gateway view inherits the app's typography, dark-mode, and surface chrome. Convert all rem sizes to px since the app's `html { font-size: 62.5% }` makes 1rem = 10px, not the 16px the components were originally written against. Filters + attribution - Multi-select filter row (provider, connection, model, app/API key, user, role, endpoint, status); sent as repeated query params to /_internal/ai/usage. Active selections render as removable chips with a clear-all button. Clicking any bars-chart row drills into that filter (provider, user, role, app, connection). - New "By app / API key" panel powered by ai_usage_log.app_id, plus a By model panel. - Estimated cost summary tile showing total_cost_usd from the backend (which is computed at log-time from the per-service rate sheet, so it matches the customer's provider invoice). MCP section - Loads /_internal/ai/mcp-usage in parallel with /_internal/ai/usage. - Renders summary + 6 panels (clients, tools, users, services, apps, methods) under a collapsible "External MCP traffic" section with an explicit disclaimer: token cost is borne by the calling AI agent, not billed to DreamFactory. - Bytes (in/out) replaces tokens in MCP rows since MCP is data proxying, not LLM inference.
…ators
Three small cleanups in the Gateway dashboard:
EMPTY_FILTERS was a shared singleton — `{ ...EMPTY_FILTERS }` only
shallow-copies, so all consumers ended up sharing the inner
provider/service_id/etc. arrays. clearFilters() worked around this by
wiping the arrays then re-creating them. Replaced with a
createEmptyFilters() factory; clearFilters is now one assignment +
refresh, and the caller can't accidentally mutate sibling state.
toggleFilter / removeChip dropped their nested generics — the
keyof UsageFilters constraint forced us to cast values to `never`,
which was a type-honesty smell. New signature takes
(keyof UsageFilters, string | number) directly; one cast (Array<string|number>)
remains where TS can't unify the per-key array type.
Cost estimator title + hint: clarified that the panel is a what-if
calculator, not the source-of-truth for spend. The summary tile's
"Estimated cost" comes from the server-stored cost_usd column (computed
at log-time from each AI Connection's configured rate sheet); the
estimator lets you experiment with different rates against the same
token counts without editing your services. Edits don't persist.
Tooltips - Help icon on the page title explaining what the dashboard tracks. - matTooltip on every summary tile (Requests, Input/Output/Total tokens, Errors, Avg latency, Estimated cost) — explains how each metric is computed and what it means for a team lead. - matTooltip on every filter field describing what dimension it filters by. - Help icon next to each bars-panel title (new optional `hint` input on df-usage-bars) — explains what the panel shows and how to read it. - matTooltip on every MCP summary tile and on the Refresh / time-range controls. Visual style pass - Two section headers (DreamFactory AI usage / External MCP traffic) with an h3 + subtitle pattern instead of the old dashed border-top — cleaner rhythm between sections and explicit framing of what each section means. - "Filters" label + drill-down hint above the filter row so users understand the row before they touch it. Active-chip row gains a thin top divider. - MCP summary uses tighter spacing than the AI summary (secondary content) and a slightly smaller hero number — visual hierarchy signals "auxiliary data" without needing words. - Disclaimer banner on the MCP section reworded to be less wall-of-text; the long form lives in the help tooltip on the section title. - Dark-mode coverage on every new element.
…default-rates from backend Frontend half of the dashboard upgrade round. Latency tile (replaces the avg-latency tile) - Shows p50 prominently with p95/p99 as a smaller compact row underneath. - Tooltip explains why p50/p95 matter and that avg lies in the presence of slow tails. - p50 also drives the period-over-period delta arrow (representative of the typical experience, unlike avg). Error breakdown panel - New "Error breakdown" bars panel in the AI section. Only renders when errors > 0. Maps the backend's 10 error classes to human labels (Timeout / Rate-limited / Auth failure / Model not found / Context too long / Connectivity / Provider 5xx / Provider 4xx / Other / Unknown). Period comparison + sparklines on every summary tile - Service now sends compare=1 by default; bundle.raw.previous gets the immediately-preceding window's totals. - Each summary tile gets a delta arrow (↑/↓) + signed % change, color- coded by metric polarity (errors/cost/latency = red on up; tokens/ requests = neutral). - New df-sparkline standalone component renders a tiny 24px-tall inline SVG of the per-day series alongside each tile. Just polyline + filled area, no axes / labels — visual texture, not data. - Sparklines pull straight out of the existing daily series, so no extra API call. Budgets - Top-of-page warning banner if any AI Connection is projected to exceed its monthly_budget_usd. Lists the offenders with current spend / projection / % of budget. - New "Budget status" card in the AI section with one row per connection: progress bar showing spent-to-date + a hatched extension showing projected month-end. Bar turns red and the amount goes red when projected to overshoot. DEFAULT_RATES single source of truth - bundle.raw.default_rates now comes from the backend's UsageRates::DEFAULT_RATES; cost-estimator reads this via a new defaultRates input instead of importing a hardcoded const. - utils/cost.ts keeps a small FALLBACK_RATES const used only when the bundle hasn't loaded yet — annotated NOT to be the source of truth.
…having file service selector
…charts + expensive calls Builds out the AI Usage Analytics page to surface the full analytical output the backend now produces (df-ai PR #3 added five new keys to /_internal/ai/usage). Answers the question every CTO asks first: "where is my money being spent on AI?" New visible sections (between the existing time-series and the bars grid, under a "Where is my money going?" section header): - Four stacked cost-over-time charts (custom SVG, no library dep): Cost by model — top 10 models by spend + Other layer Cost by provider — Anthropic / OpenAI / xAI / local breakdown Cost by user — top 10 spenders + Other (id → display name) Cost by app/key — per-tenant cost visibility (id → app name) Each layer is auto-coloured from a fixed palette; the long-tail "Other" bucket is always rendered last in muted gray so legends are stable across refreshes. - Top 10 most expensive calls table — full attribution per row (cost, model, user, app, connection, tokens in/out, latency, status, when). Rows tinted by status (error red, partial amber). The drill-down hook for outliers averages otherwise hide. Augmentations to existing widgets: - by_model bars now show effective per-1k-token rate as a small purple badge (e.g. "$0.0046/1k") next to the totals — answers "is this premium model worth the rate" without the user doing math - New "Partials" summary tile (only renders when partials > 0): streaming requests where the client disconnected before the model finished. Tokens delivered are still billed; tracked separately so an uptick (usually network/timeout) doesn't masquerade as a healthy success rate. Tinted amber to match severity vs the red error tile. Components added: - df-cost-by-dimension/ — generic stacked-area for series_by_<dim> data. One component, four instances (model/provider/user/app). Pure SVG, deterministic colour palette, Other bucket sentinel ('__other__') matches the backend wire contract. Renders empty-state cleanly. - df-expensive-calls/ — Material-table-style display for the top-N rows. Status pill (success/error/partial) with semantic colors, monospace cost column, hover tooltip for full timestamp. Type extensions: - UsageResponse: + partials, + series_by_{model,user,app,provider}, + most_expensive_calls - ModelRowRaw: + cost_per_1k_tokens - GroupRow: + costPer1kTokens (optional, populated only on by_model) - UsageSummary: + partials - New: DimensionSeriesRowRaw, ExpensiveCallRowRaw, OTHER_BUCKET sentinel The backend's series_by_X rows are normalized HERE in the parent component (id → display label via bundle.users / bundle.apps maps), so the chart component itself stays generic and reusable. dist/ rebuilt fresh — `ng build` completed clean (warnings only from the pre-existing swagger-ui CommonJS deps; no new ones introduced). Verified live against /_internal/ai/usage on the dev box: 31 requests, $0.0095 spend, 5 model rows with cost_per_1k populated (sonnet at $0.0046/1k vs haiku at $0.0034/1k — the exact "should we move to the cheaper model" answer), 5 series_by_model rows including 1 streamed.
…fallback
Two issues surfaced during CEO-meeting QA on the AI screens.
1. Font sizes too small across the AI Chat UI, AI Chat services config,
and the shared AI form components (test-connection, model-picker,
allowed-roles). Cause: those components used rem-based sizes (0.875rem,
1rem, 1.2rem, etc.) authored against the standard 16px html baseline.
DF's html sets font-size: 62.5% so 1rem = 10px — those values rendered
at 8.75px / 10px / 12px instead of the intended 14px / 16px / 19px.
The AI Usage tab already uses absolute px values and looks correct;
this brings every other AI surface into the same convention.
Bulk-converted across:
src/app/adf-ai-chat/**/*.{ts,scss} (5 files)
src/app/shared/components/df-ai-test-connection/
src/app/shared/components/df-ai-model-picker/
src/app/shared/components/df-ai-allowed-roles/
Conversion table (rem → px to preserve the originally-intended size on
DF's 62.5% html baseline):
0.7rem → 11px 0.95rem → 15px 1.1rem → 18px
0.75rem → 12px 1rem → 16px 1.2rem → 19px
0.8125rem → 13px 1.05rem → 17px 1.25rem → 20px
0.875rem → 14px
Zero remaining rem font-size declarations across the AI screens.
2. Test Connection button on AI Connections returned "401 Invalid API
key" for openai_compatible providers (e.g. local llama-server with
--api-key enabled). Root cause: the form doesn't redisplay saved
secrets, so when the admin clicked Test on an existing connection, the
api_key field was blank and the backend made an unauthenticated
listModels() call. Fix has two halves:
- Backend (df-ai PR #3 follow-up commit): testConnection accepts
`service_id` and falls back to the saved AiConnectionConfig for
any field the form left blank. Provider takes form values when
present, saved values otherwise.
- Frontend (this commit): df-ai-test-connection has a new
@input() serviceId; df-service-details passes serviceData.id when
edit=true; the runtime validator skips the "missing api_key"
rejection when serviceId is set (the backend will fall back).
Also updated the validation message to mention "Existing connections
fall back to the saved key automatically" so admins know they can
just click Test on a saved connection without retyping.
dist/ rebuilt clean (warnings only from pre-existing swagger-ui CommonJS
deps; no new ones).
…ent script service config screen
…s' into local/l13-ai-integration
…d-path Fix/event script add path
The April 2026 security update bumped swagger-ui 4.15.5 -> ^5.32.2 without migrating df-api-docs.component.ts to the v5 calling convention. v5 introduced breaking changes in bundle layout and internal call paths, causing every service's API docs to throw "TypeError: o is not a function" from inside the swagger-ui bundle. Pinning back to 4.15.5 restores the working UI as a stopgap. The patched DOMPurify XSS / Handlebars JS injection CVEs return — low practical risk for the admin UI (specs come from trusted backends), but a proper v5 migration is still owed. Rebuilt dist/ artifacts included.
…ategy href RouterTestingModule uses PathLocationStrategy by default, so [routerLink] renders as /api-connections in the test bed, not #/api-connections. The production app provides withHashLocation() in main.ts, but the spec's intent is "tile is an anchor (supports middle-click)", not "verify the hash strategy" — so align the expectation with what TestBed actually renders.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release prep: cut develop into master/main
Releases this package's
developline for the upcoming DreamFactoryLaravel 13 + PHP 8.5 release.
Verification
The develop HEAD on this branch is the SHA locked into the L13/PHP 8.5
test bundle that the team has been smoking. Full end-to-end smoke
passed 2026-05-26: PHP 8.5.5, Laravel 13.11.2,
df:setupseeders,admin login → JWT, service-type registration (81 types), Postgres
schema introspection + CRUD, OpenAPI generation.
Coordinated release PRs
This is one of ~26 coordinated
develop → master/mainPRs openedtogether as the release cut. Suggested merge order:
Customer upgrade path
In-place on Docker / managed Linux packages; blue-green strongly
recommended on Windows + custom-PHP-extension installs. Pre-flight
checklist in the release notes.