Skip to content

feat: Plan review and the AI assistant in the dashboard#67

Merged
nfebe merged 7 commits into
mainfrom
feat/ai-native
Jun 7, 2026
Merged

feat: Plan review and the AI assistant in the dashboard#67
nfebe merged 7 commits into
mainfrom
feat/ai-native

Conversation

@nfebe

@nfebe nfebe commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Front-end for the agent's plan/apply and AI features. Depends on the agent feat/ai-native PR; merge that first (builds against its new endpoints).

Plan review

  • When a deployment requires plan review, Save/Delete/Setup/service actions transparently switch to a preview modal: summary counts, per-change reasons, expandable before/after diffs, sensitive values masked. Apply/discard the saved plan; stale previews surface as a warning.
  • Deployment settings moved under Configuration as a reusable sub-tab; split buttons target the whole deployment or a single service.

AI assistant

  • Global launcher in the header opens a chat against the whole instance; the same chat is seeded from any log view or operation result.
  • Shows the assistant's investigation inline (each read-only lookup as an expandable step); proposed lookups get CLI-style Allow/Decline buttons per the session's auto-run/approve toggle.
  • Suggested fixes run only on click through the guarded APIs. Bulky logs go to the model as hidden context, not echoed into the transcript.
  • New AI Settings tab; new features use lucide icons (PrimeIcons being phased out).

413 tests, lint and build green.

nfebe added 6 commits June 7, 2026 18:53
Mutating actions run directly by default. When a deployment has plan
review required, the same buttons transparently switch to a preview
flow: the change is computed server side and shown in a review modal
with summary counts, per-change reasons and expandable before/after
diffs, with sensitive values masked behind an explicit reveal. Apply
and discard act on the saved plan, and a stale preview surfaces as a
warning instead of applying blindly.

Deployment settings moved under the Configuration tab, which now uses
a reusable attached sub-tab component, and were restyled as a single
expandable list with a Require Plan Review switch next to Protected
Mode. Start, stop, restart, rebuild and pull are split buttons that
target the whole deployment or any single service, and each service
row gains its own start, stop, restart and rebuild controls.
Every surface that shows logs now carries an AI action in the log
toolbar: deployment logs, operation output in both operation modals,
and any future use of the shared log viewer. The assistant opens in
one global modal where the user can switch between diagnose, improve,
secure and explain, and ask free-text follow-up questions against the
same context. When AI is not configured, the entry points stay visible
and lead to the settings page instead of silently hiding.

Analyses can return suggested actions; each is shown with its command
or action and reasoning, and runs only on explicit click through the
same APIs a human would use, so permissions, protected mode and plan
review apply unchanged, with output shown inline. Failed operations
default to diagnosis and successful ones to explanation.

The assistant is configured from a new settings tab: any
OpenAI-compatible endpoint, model name and a write-only API key,
applied to the running agent without a restart.
The AI icon used a glyph that only exists in PrimeIcons 7 while the
app pinned version 6, so every assistant button rendered as an empty
space; the icon set is upgraded, which is additive and keeps all
existing icons. The assistant also kept telling users to enable AI
after they had just configured it, because the enabled status was
cached from before setup; the status is now re-checked when it reads
disabled and refreshed immediately after saving AI settings.
All AI entry points (log toolbar action, assistant modal header and
hints, the assist button, the AI settings tab) now render the lucide
sparkles icon instead of a PrimeIcons glyph that only exists in a
newer icon-font version than the app ships, which left every AI button
as an empty space. The icon set version is reverted; new features
should use lucide as the project moves away from PrimeIcons, and the
settings tab bar can now render lucide icons alongside the legacy
class-based ones.
The AI assistant is now a chat, not a one-shot answer. A persistent
launcher sits top-center in the dashboard header whenever AI is
enabled, opening a general session where you can ask about the
instance, its deployments, networks, or anything going wrong, and keep
asking follow-ups in the same thread.

The chat shows the assistant's investigation inline: each read-only
lookup it runs (or proposes) appears as an expandable step with its
result, so you see how it reached an answer. A per-session toggle
chooses whether those lookups run automatically or pause for your
approval, and pending lookups are shown with allow or decline before
anything runs. Suggested fixes still execute only on click through the
guarded APIs.

Every place that shows logs (deployment logs, operation output) and
every operation modal opens this same chat, seeded with what you were
looking at, so the assistant starts from your context and can dig
further with its tools.
… transcript

A proposed read-only lookup now shows with Allow and Decline buttons
beside it, like a CLI tool prompt: a single pending call resolves on
one click, and when several are proposed each gets its own decision
(with allow-all and decline-all), showing each outcome as the batch
runs.

Seeded analyses (from a log view or an operation result) send the logs
or output to the model as hidden context while the conversation shows
only a short prompt, so the chat no longer echoes a wall of log lines.
The header launcher is larger and now reads as running the instance,
not just asking, and the empty prompt invites both questions and
actions.
@sourceant

sourceant Bot commented Jun 7, 2026

Copy link
Copy Markdown

Code Review Summary

This PR introduces a robust Plan/Apply flow and an AI assistant integrated into the dashboard. It successfully bridges the gap between raw operations and user-reviewed changes, particularly for destructive actions.

🚀 Key Improvements

  • Integrated Plan Review modal with diffing and sensitive value masking.
  • AI Assistant for log analysis and operation troubleshooting with 'Auto-run' lookup capabilities.
  • Reorganized Deployment Settings into a sub-tab under Configuration.
  • Introduced SplitActionButtons to support scoped actions (Deployment vs Service).

💡 Minor Suggestions

  • Consolidate AI context generation logic.
  • Add console logging for failed AI JSON parsing.
  • Truncate logs in AI context to prevent token overflow.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread src/components/ai/AssistChat.vue Outdated
};

const renderMarkdown = (content: string) =>
DOMPurify.sanitize(marked.parse(content, { async: false }) as string);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default marked.parse behavior can include IDs and other attributes that might bypass basic sanitization or cause CSS collisions. It is safer to explicitly configure the sanitizer to allow only a safe subset of tags, especially when dealing with AI-generated markdown which can be unpredictable.

Suggested change
DOMPurify.sanitize(marked.parse(content, { async: false }) as string);
DOMPurify.sanitize(marked.parse(content, { async: false }) as string, { USE_PROFILES: { html: true } });

const parts = computed(() => diffLines(props.before ?? "", props.after ?? ""));

const partLines = (part: ChangeObject<string>) => {
const lines = part.value.split("\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the partLines function, using lines.pop() modifies the array if the last element is empty. While functional, it's slightly cleaner to filter or slice to handle the trailing newline often returned by diffing libraries.

Suggested change
const lines = part.value.split("\n");
const lines = part.value.split("\n");
return lines.length > 1 && lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 7, 2026

Copy link
Copy Markdown

Deploying flatrun-ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7d5ee52
Status: ✅  Deploy successful!
Preview URL: https://01abb2d4.flatrun-ui.pages.dev
Branch Preview URL: https://feat-ai-native.flatrun-ui.pages.dev

View logs

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment on lines +25 to +26

const parts = computed(() => diffLines(props.before ?? "", props.after ?? ""));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When both 'before' and 'after' are null, diffLines might return an empty array or unexpected result. While types allow null, the UI should ideally handle the empty state explicitly or prevent rendering if both are missing.

Suggested change
const parts = computed(() => diffLines(props.before ?? "", props.after ?? ""));
const parts = computed(() => {
if (props.before === null && props.after === null) return [];
return diffLines(props.before ?? "", props.after ?? "");
});

const detail = args.command || args.path || args.service || args.deployment || "";
return detail ? `${humanTool(call.name)}: ${detail}` : humanTool(call.name);
};
const parseArgs = (raw: string): Record<string, string> => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON.parse can fail if the AI returns malformed arguments. While there is a catch block, logging the error to console might help debug integration issues with different LLM providers.

Suggested change
const parseArgs = (raw: string): Record<string, string> => {
const parseArgs = (raw: string): Record<string, string> => {
try {
return JSON.parse(raw || "{}");
} catch (e) {
console.error("AI Assistant: Failed to parse tool arguments", raw, e);
return {};
}
};

@nfebe nfebe merged commit aa47ce6 into main Jun 7, 2026
5 checks passed
@nfebe nfebe deleted the feat/ai-native branch June 7, 2026 22:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant