Skip to content

Conversation

@lollipopkit
Copy link
Owner

@lollipopkit lollipopkit commented Jan 30, 2026

Fixes #1038


Open with Devin

Summary by CodeRabbit

Release Notes

  • New Features
    • Added AI Assistant floating button for quick access to AI help across the app
    • AI-assisted command generation for container management
    • AI-assisted command suggestions in SSH terminal sessions
    • AI help for systemd service operations
    • AI safety protections with risk assessment and sensitive data redaction
    • AI interaction history tracking

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

This pull request introduces comprehensive AI assistance features including context management, safety utilities for redaction and risk classification, UI components for AI interactions (global FAB overlay and assist sheet), and integrates AI-assisted workflows across multiple container, system, and SSH pages. It also adds persistent AI history and FAB position settings.

Changes

Cohort / File(s) Summary
Core AI Infrastructure
lib/data/provider/ai/ai_context.dart, lib/data/provider/ai/ai_safety.dart, lib/data/model/ai/ask_ai_models.dart, lib/data/provider/ai/ask_ai.dart
Added AiContextSnapshot with Riverpod provider for managing AI context. Introduced AiSafety class with redaction (private keys, tokens, API keys, identities) and risk classification (high/medium/low). Extended AskAiCommand with risk, needsConfirmation, why, and prechecks fields. Added AskAiScenario enum (general, terminal, systemd, container, process, snippet, sftp) with scenario-specific prompts.
Storage & Persistence
lib/data/store/ai_history.dart, lib/data/res/store.dart, lib/data/store/setting.dart
Added AiHistoryStore for persistent AI conversation history via Hive. Registered AiHistoryStore in Stores dependency injection. Added aiFabOffsetX and aiFabOffsetY settings for normalized FAB position persistence.
AI UI Widgets
lib/view/widget/ai/ai_assist_sheet.dart, lib/view/widget/ai/ai_fab_overlay.dart, lib/app.dart
Introduced AiAssistSheet ConsumerStatefulWidget for streaming AI interactions with command risk assessment, confirmation flow, and apply behaviors (insert, openSsh, copy). Added AiFabOverlay widget for draggable global AI FAB with context-aware scenarios. Wrapped app responsive child with AiFabOverlay overlay.
Menu & Container Integration
lib/data/model/app/menu/container.dart, lib/view/page/container/actions.dart, lib/view/page/container/container.dart
Added ContainerMenu.askAi enum value mapping to smart_toy_outlined icon. Implemented container AI action handler building context blocks (runtime, id, name, image, status) and invoking AI assist sheet.
Page Integrations
lib/view/page/ssh/page/ask_ai.dart, lib/view/page/ssh/page/page.dart, lib/view/page/systemd.dart, lib/view/page/server/detail/view.dart
Refactored SSH Ask AI page from full streaming chat UI to streamlined command insertion workflow with sensitive data redaction. Added systemd page AI action with scope/item context. Added server detail view AI assistant button navigating to SSH page. Updated imports for AI safety and assist sheet.
Testing
test/ai_safety_test.dart
Added comprehensive unit tests for AiSafety redaction (private keys, bearer tokens, API keys, Spi identity) and risk classification (high/medium/low risk patterns).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AiFabOverlay
    participant AiContextProvider
    participant AiAssistSheet
    participant AskAiRepository
    participant OpenAI as OpenAI API
    participant AiSafety
    
    User->>AiFabOverlay: Tap FAB
    AiFabOverlay->>AiContextProvider: Read current context
    AiContextProvider-->>AiFabOverlay: Return AiContextSnapshot
    AiFabOverlay->>AiAssistSheet: Show with scenario & context blocks
    AiAssistSheet->>AiSafety: Redact sensitive blocks
    AiSafety-->>AiAssistSheet: Redacted blocks
    User->>AiAssistSheet: Enter prompt
    AiAssistSheet->>AskAiRepository: ask(scenario, contextBlocks)
    AskAiRepository->>OpenAI: Stream request with scenario prompt
    OpenAI-->>AskAiRepository: Stream responses (delta, tool_call)
    AskAiRepository-->>AiAssistSheet: AskAiEvent stream
    AiAssistSheet->>AiSafety: classifyRisk(command)
    AiSafety-->>AiAssistSheet: AiCommandRisk
    AiAssistSheet->>AiAssistSheet: Risk confirmation if needed
    User->>AiAssistSheet: Apply command
    AiAssistSheet->>User: Execute callback (insert/openSsh/copy)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #1004: Implements minimize/fold behavior in the new AiAssistSheet, directly building upon similar UI state management from the previous Ask AI sheet implementation.
  • #940: Modifies AskAiRepository.\_buildRequestBody's request/prompt construction, sharing core logic changes with this PR's scenario-driven prompt enhancement.
  • #936: Updates lib/app.dart's app-building logic, paralleling this PR's wrapping of the responsive child with AiFabOverlay.
🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'proposal: enhance AI functionalities' is overly vague and generic, using non-descriptive terms that don't convey the specific changes made in the changeset. Revise the title to be more specific and descriptive of the primary changes, such as 'Add AI assist sheet widget and safety utilities' or 'Implement AI-powered command assistance features'.
Linked Issues check ❓ Inconclusive The linked issue #1038 has no description or requirements provided, making it impossible to validate whether code changes meet the stated objectives. Ensure the linked issue #1038 contains detailed requirements and acceptance criteria so the code changes can be properly validated against those objectives.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Out of Scope Changes check ✅ Passed Without detailed requirements in the linked issue, all changes appear to be part of a cohesive AI enhancement feature set covering models, safety, context, sheets, and UI integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch lollipopkit/issue1038

Tip

🧪 Unit Test Generation v2 is now available!

We have significantly improved our unit test generation capabilities.

To enable: Add this to your .coderabbit.yaml configuration:

reviews:
  finishing_touches:
    unit_tests:
      enabled: true

Try it out by using the @coderabbitai generate unit tests command on your code files or under ✨ Finishing Touches on the walkthrough!

Have feedback? Share your thoughts on our Discord thread!


Comment @coderabbitai help to get the list of available commands and usage tips.

@lollipopkit lollipopkit marked this pull request as draft January 30, 2026 07:25
@gemini-code-assist
Copy link

Summary of Changes

Hello @lollipopkit, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the application's AI capabilities by introducing a more structured, context-aware, and secure AI assistance framework. It provides a unified UI for AI interactions, implements robust safety mechanisms for command suggestions, and integrates AI support into critical operational workflows, aiming to deliver a more intelligent and helpful user experience for server management.

Highlights

  • New AI Assist Sheet and Global FAB: A new generic AiAssistSheet widget has been introduced to handle AI conversations, replacing the previous SSH-specific implementation. Complementing this, a global, draggable AiFabOverlay provides quick access to AI assistance from anywhere in the application, with its position persisted across sessions.
  • Enhanced AI Context Management: A new AiContextNotifier and AiContextSnapshot system has been implemented to manage the AI conversation context, including the current title, scenario, relevant code blocks, and associated server ID. This allows for more tailored and context-aware AI interactions.
  • AI Safety and Command Risk Classification: A new AiSafety utility class has been added to redact sensitive information from AI prompts (e.g., private keys, API keys, server identity) and to classify the risk level of suggested commands (low, medium, high). This enhances security and provides users with critical safety information before executing AI-generated commands.
  • Extended AI Command Model and Scenario-Based Prompting: The AskAiCommand model now includes fields for risk, needsConfirmation, why, and prechecks, enabling richer and safer command suggestions. The AI repository also supports various AskAiScenario types (e.g., general, terminal, systemd, container), allowing the AI to provide more relevant advice based on the user's current task.
  • AI History Storage: A dedicated AiHistoryStore has been created to persist AI conversation history, ensuring that AI interactions are saved separately from other application history types.
  • Integration Across Application Features: AI assistance has been integrated into key areas: a new 'Ask AI' option is available in the container menu, a dedicated 'Ask AI' button is present on the server detail page, and the SSH terminal's AI assist sheet now leverages the new generic sheet and includes terminal scrollback as context for improved AI understanding.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant and well-executed enhancement to the app's AI functionalities. The changes are impressive, focusing on making the AI assistance more context-aware, modular, and, most importantly, safe. Key additions include a global AI floating action button, a generic AI assistant sheet, context providers, and a robust safety layer for redacting sensitive data and classifying command risk. The introduction of unit tests for the safety features is also a great practice. I've included a few suggestions to further improve robustness and user experience consistency.


final _rxRmRf = RegExp(r'\brm\b[^\n\r]*\s-[a-z-]*r[a-z-]*f[a-z-]*\b');

final _rxChmodChownRoot = RegExp(r'\b(chmod|chown)\b[^\n\r]*\s-\w*r\w*\b[^\n\r]*\s/\b');

Choose a reason for hiding this comment

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

high

The current regex for detecting recursive chmod/chown on the root directory is a good start but doesn't handle the long-form --recursive flag. This could cause a high-risk command to be missed by the risk classifier. Adding an alternative for --recursive will make the detection more robust.

Suggested change
final _rxChmodChownRoot = RegExp(r'\b(chmod|chown)\b[^\n\r]*\s-\w*r\w*\b[^\n\r]*\s/\b');
final _rxChmodChownRoot = RegExp(r'\b(chmod|chown)\b[^\n\r]*\s(-\w*r\w*|--recursive)\b[^\n\r]*\s/\b');

Comment on lines +362 to +366
List<String>? prechecks;
final preRaw = decoded['prechecks'];
if (preRaw is List) {
prechecks = preRaw.map((e) => e.toString()).where((e) => e.trim().isNotEmpty).toList();
}

Choose a reason for hiding this comment

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

medium

The current logic for parsing prechecks converts every item in the list to a string using .toString(). This is a bit too permissive and could hide potential issues if the AI model returns a non-string type (e.g., a number) in the list. It's safer to filter for items that are already strings to ensure type safety.

Suggested change
List<String>? prechecks;
final preRaw = decoded['prechecks'];
if (preRaw is List) {
prechecks = preRaw.map((e) => e.toString()).where((e) => e.trim().isNotEmpty).toList();
}
List<String>? prechecks;
final preRaw = decoded['prechecks'];
if (preRaw is List) {
prechecks = preRaw.whereType<String>().where((e) => e.trim().isNotEmpty).toList();
}

Comment on lines +15 to +17
fromObj: (val) => List<Map<String, dynamic>>.from(
(val as List).map((e) => Map<String, dynamic>.from(e as Map)),
),

Choose a reason for hiding this comment

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

medium

The current implementation of fromObj uses as for casting, which is unsafe and can lead to runtime errors if the data from storage is not in the exact expected format. Using type checks like is and whereType will make this data parsing more robust and prevent potential crashes.

    fromObj: (val) {
      if (val is! List) return const [];
      return val.whereType<Map>().map((e) => Map<String, dynamic>.from(e)).toList();
    },

Comment on lines +130 to +136
icon: const Icon(Icons.smart_toy_outlined),
tooltip: context.l10n.askAi,
onPressed: () {
final args = SshPageArgs(spi: si.spi);
SSHPage.route.go(context, args);
},
),

Choose a reason for hiding this comment

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

medium

This "Ask AI" button navigates to the SSH page, which is inconsistent with other AI entry points in the app that directly open the AI assistant sheet with relevant context. For a better and more consistent user experience, this button should open the AiAssistSheet with general server status information as context.

Note: You will need to add the following imports to the file:

import 'package:server_box/data/provider/ai/ask_ai.dart';
import 'package:server_box/view/widget/ai/ai_assist_sheet.dart';
          icon: const Icon(Icons.smart_toy_outlined),
          tooltip: context.l10n.askAi,
          onPressed: () {
            final serverState = ref.read(serverProvider(si.spi.id));
            final status = serverState.status;
            final blocks = [
              '[Server Info]\nName: ${si.spi.name}\nOS: ${status.osPrettyName}\nUptime: ${status.uptime}',
              '[CPU Usage]\n${status.cpu.usedPercent(coreIdx: 0).toStringAsFixed(1)}%',
              '[Memory Usage]\n${(status.mem.usedPercent * 100).toStringAsFixed(1)}%',
            ];
            showAiAssistSheet(
              context,
              AiAssistArgs(
                title: context.l10n.askAi,
                contextBlocks: blocks,
                scenario: AskAiScenario.general,
                applyLabel: libL10n.ok,
                applyBehavior: AiApplyBehavior.openSsh,
                onOpenSsh: (cmd) {
                  final args = SshPageArgs(spi: si.spi, initCmd: cmd);
                  SSHPage.route.go(context, args);
                },
              ),
            );
          },

Comment on lines +176 to +183
final inserted = _seenCommands.add(event.command.command);
if (inserted) {
final risk = event.command.risk != null
? (AiCommandRiskX.tryParse(event.command.risk) ?? AiSafety.classifyRisk(event.command.command))
: AiSafety.classifyRisk(event.command.command);
_chatEntries.add(_ChatEntry.command(event.command, risk));
shouldScroll = true;
}

Choose a reason for hiding this comment

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

medium

The logic for determining the risk of a command is duplicated here and again in the AskAiCompleted event handler (lines 191-199). To improve maintainability and adhere to the DRY (Don't Repeat Yourself) principle, this logic should be extracted into a private helper method within the _AiAssistSheetState class.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View issue and 6 additional flags in Devin Review.

Open in Devin Review


final _rxDdToBlockDevice = RegExp(r'\bdd\b[^\n\r]*\bof\s*=\s*/dev/');

final _rxRmRf = RegExp(r'\brm\b[^\n\r]*\s-[a-z-]*r[a-z-]*f[a-z-]*\b');

Choose a reason for hiding this comment

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

🔴 Risk classification regex fails to detect rm -fr command variant

The _rxRmRf regex pattern in ai_safety.dart fails to detect the common rm -fr command variant (where -f comes before -r), allowing it to be classified as low risk instead of high risk.

Click to expand

Analysis

The regex pattern at line 166 is:

final _rxRmRf = RegExp(r'\brm\b[^\n\r]*\s-[a-z-]*r[a-z-]*f[a-z-]*\b');

This pattern requires r to appear before f in the flags ([a-z-]*r[a-z-]*f[a-z-]*).

Commands that match (high risk detected):

  • rm -rf /
  • rm -rfi /

Commands that DON'T match (incorrectly classified as low risk):

  • rm -fr /
  • rm -fR / ✗ (though case is lowered, -fr order still fails)

Impact

rm -fr is equally common and dangerous as rm -rf. Users could execute a destructive command suggested by AI without the appropriate high-risk countdown confirmation dialog, since AiSafety.classifyRisk('rm -fr /') returns AiCommandRisk.low instead of AiCommandRisk.high.

Recommendation

Modify the regex to match either flag order, e.g.:

final _rxRmRf = RegExp(r'\brm\b[^\n\r]*\s-[a-z-]*([rf][a-z-]*[rf])[a-z-]*\b');

Or use two patterns:

final _rxRmRf = RegExp(r'\brm\b[^\n\r]*\s-[a-z-]*(rf|fr)[a-z-]*\b');

Recommendation: Change the regex to detect both rm -rf and rm -fr variants: RegExp(r'\brm\b[^\n\r]*\s-[a-z-]*(rf|fr)[a-z-]*\b')

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/data/res/store.dart (1)

14-47: ⚠️ Potential issue | 🟡 Minor

Add @lollipopkit to the participants field in lib/data/res/github_id.dart.
Issue #1038 was filed by @lollipopkit, but this contributor is not listed in the participants set.

🤖 Fix all issues with AI agents
In `@lib/data/provider/ai/ai_safety.dart`:
- Line 166: The _rxRmRf regex only matches when the 'r' flag appears before 'f'
(e.g., "-rf"), so update detection to handle any flag ordering by either
creating two regexes (e.g., _rxRmRecursive and _rxRmForce) that independently
detect recursive and force flags and then modify classifyRisk to treat a string
as high risk when both patterns match, or change _rxRmRf to a pattern that
asserts presence of both flags in any order; reference _rxRmRf and classifyRisk
when applying the fix.

In `@lib/view/widget/ai/ai_fab_overlay.dart`:
- Around line 24-36: The restored FAB position in didChangeDependencies can land
off-screen; update the logic that computes _offsetPx to clamp the computed
Offset into the visible bounds: obtain MediaQuery.of(context).size and
MediaQuery.of(context).padding (or viewInsets as needed), compute min/max X and
Y allowed (e.g. between left padding and width - right padding, and top padding
and height - bottom padding, optionally considering the FAB size), then clamp
Stores.setting.aiFabOffsetX.fetch() and aiFabOffsetY.fetch() into [0,1] as
before but map them into those constrained pixel ranges before assigning
_offsetPx so the FloatingActionButton stays on-screen.
🧹 Nitpick comments (4)
lib/app.dart (1)

112-115: Guard against a null child in the builder.

MaterialApp.builder can receive a null child; using a safe fallback avoids a potential NPE in the overlay wrapper.

Suggested tweak
-        final responsiveChild = ResponsivePoints.builder(context, child);
-        return AiFabOverlay(child: responsiveChild);
+        final safeChild = child ?? const SizedBox.shrink();
+        final responsiveChild = ResponsivePoints.builder(context, safeChild);
+        return AiFabOverlay(child: responsiveChild);
lib/data/provider/ai/ai_context.dart (2)

14-16: Mutable List<String> in @immutable class can break immutability guarantees.

The blocks field is a mutable List<String>. Callers could mutate it after construction, breaking the immutability contract. Consider using List<String>.unmodifiable() in the constructor or storing as IList from fast_immutable_collections.

♻️ Option: Defensive copy in constructor
 const AiContextSnapshot({
   required this.title,
   required this.scenario,
-  required this.blocks,
+  required List<String> blocks,
   this.spiId,
   this.updatedAtMs,
-});
+}) : blocks = blocks;

+// Or for true immutability:
+AiContextSnapshot({
+  required this.title,
+  required this.scenario,
+  required List<String> blocks,
+  this.spiId,
+  this.updatedAtMs,
+}) : blocks = List.unmodifiable(blocks);

37-37: Consider using riverpod_annotation for consistency.

Based on learnings, the codebase prefers Riverpod with code generation. This provider uses the manual approach. If the rest of the codebase uses @riverpod annotations, consider aligning for consistency.

lib/view/widget/ai/ai_assist_sheet.dart (1)

541-544: Minor: Trailing SizedBox after last context block.

The loop adds spacing after each block including the last one. This creates 8px extra padding before the column ends. Not a bug, but could be refined if pixel-perfect layout is desired.

♻️ Optional: Remove trailing spacer
 child: Column(
   crossAxisAlignment: CrossAxisAlignment.start,
   children: [
-    for (final block in widget.args.contextBlocks) ...[
-      SelectableText(block, style: const TextStyle(fontFamily: 'monospace')),
-      const SizedBox(height: 8),
-    ],
+    for (var i = 0; i < widget.args.contextBlocks.length; i++) ...[
+      SelectableText(widget.args.contextBlocks[i], style: const TextStyle(fontFamily: 'monospace')),
+      if (i < widget.args.contextBlocks.length - 1) const SizedBox(height: 8),
+    ],
   ],
 ),
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64dc004 and 28ac614.

📒 Files selected for processing (18)
  • lib/app.dart
  • lib/data/model/ai/ask_ai_models.dart
  • lib/data/model/app/menu/container.dart
  • lib/data/provider/ai/ai_context.dart
  • lib/data/provider/ai/ai_safety.dart
  • lib/data/provider/ai/ask_ai.dart
  • lib/data/res/store.dart
  • lib/data/store/ai_history.dart
  • lib/data/store/setting.dart
  • lib/view/page/container/actions.dart
  • lib/view/page/container/container.dart
  • lib/view/page/server/detail/view.dart
  • lib/view/page/ssh/page/ask_ai.dart
  • lib/view/page/ssh/page/page.dart
  • lib/view/page/systemd.dart
  • lib/view/widget/ai/ai_assist_sheet.dart
  • lib/view/widget/ai/ai_fab_overlay.dart
  • test/ai_safety_test.dart
🧰 Additional context used
📓 Path-based instructions (4)
lib/data/model/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

After modifying annotated model files, run: dart run build_runner build --delete-conflicting-outputs

Files:

  • lib/data/model/ai/ask_ai_models.dart
  • lib/data/model/app/menu/container.dart
lib/view/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

lib/view/**/*.dart: Prefer widgets/utilities from fl_lib for common UI (e.g., CustomAppBar, context.showRoundDialog, Input, Btnx.cancelOk)
Prefer using libL10n strings before adding new ones to project l10n
Split UI into build, actions, and utils; use extension on to separate concerns

Files:

  • lib/view/page/container/container.dart
  • lib/view/page/ssh/page/page.dart
  • lib/view/widget/ai/ai_fab_overlay.dart
  • lib/view/page/systemd.dart
  • lib/view/widget/ai/ai_assist_sheet.dart
  • lib/view/page/server/detail/view.dart
  • lib/view/page/container/actions.dart
  • lib/view/page/ssh/page/ask_ai.dart
lib/data/provider/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Organize Riverpod providers by feature under lib/data/provider/

Files:

  • lib/data/provider/ai/ai_context.dart
  • lib/data/provider/ai/ai_safety.dart
  • lib/data/provider/ai/ask_ai.dart
lib/data/store/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Persist state using Hive stores placed under lib/data/store/

Files:

  • lib/data/store/ai_history.dart
  • lib/data/store/setting.dart
🧠 Learnings (14)
📓 Common learnings
Learnt from: GT-610
Repo: lollipopkit/flutter_server_box PR: 0
File: :0-0
Timestamp: 2026-01-22T05:10:54.502Z
Learning: For all future pull requests in the flutter_server_box repository, check if participants from issues resolved by the PR are added to the `participants` field in `lib/data/res/github_id.dart`. If any participants are missing, propose adding them. First verify if they already exist or have been added in the PR commits.
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/data/model/container/**/*.dart : Place container/Docker models under lib/data/model/container/

Applied to files:

  • lib/view/page/container/container.dart
  • lib/view/page/ssh/page/page.dart
  • lib/data/model/app/menu/container.dart
  • lib/view/page/container/actions.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/data/model/server/**/*.dart : Place server management models under lib/data/model/server/

Applied to files:

  • lib/view/page/container/container.dart
  • lib/view/page/ssh/page/page.dart
  • lib/app.dart
  • lib/data/res/store.dart
  • lib/view/page/server/detail/view.dart
📚 Learning: 2026-01-17T04:24:16.501Z
Learnt from: GT-610
Repo: lollipopkit/flutter_server_box PR: 0
File: :0-0
Timestamp: 2026-01-17T04:24:16.501Z
Learning: In lib/core/extension/ssh_client.dart and lib/data/provider/container.dart, when executing Docker/Podman commands via SSH, stderr must be included in the output (not set to stderr: false) because the error detection logic relies on stderr messages like "command not found" to properly detect when Docker/Podman is not installed and display appropriate error prompts to users.

Applied to files:

  • lib/view/page/container/container.dart
  • lib/view/page/ssh/page/page.dart
  • lib/view/page/systemd.dart
  • lib/view/page/server/detail/view.dart
  • lib/view/page/container/actions.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/data/model/ssh/**/*.dart : Place SSH models under lib/data/model/ssh/

Applied to files:

  • lib/view/page/container/container.dart
  • lib/view/page/ssh/page/page.dart
  • lib/view/page/server/detail/view.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/view/**/*.dart : Split UI into build, actions, and utils; use extension on to separate concerns

Applied to files:

  • lib/view/page/container/container.dart
  • lib/app.dart
  • lib/view/page/server/detail/view.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/data/model/sftp/**/*.dart : Place SFTP models under lib/data/model/sftp/

Applied to files:

  • lib/view/page/ssh/page/page.dart
  • lib/view/page/server/detail/view.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/view/**/*.dart : Prefer using libL10n strings before adding new ones to project l10n

Applied to files:

  • lib/view/page/ssh/page/page.dart
  • lib/app.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/view/**/*.dart : Prefer widgets/utilities from fl_lib for common UI (e.g., CustomAppBar, context.showRoundDialog, Input, Btnx.cancelOk)

Applied to files:

  • lib/view/page/ssh/page/page.dart
  • lib/view/widget/ai/ai_fab_overlay.dart
  • lib/app.dart
  • lib/view/widget/ai/ai_assist_sheet.dart
  • lib/view/page/server/detail/view.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/data/provider/**/*.dart : Organize Riverpod providers by feature under lib/data/provider/

Applied to files:

  • lib/data/provider/ai/ai_context.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Use Riverpod with code generation (riverpod_annotation) for state management

Applied to files:

  • lib/data/provider/ai/ai_context.dart
📚 Learning: 2026-01-29T08:50:01.572Z
Learnt from: GT-610
Repo: lollipopkit/flutter_server_box PR: 1031
File: lib/data/provider/container.dart:360-363
Timestamp: 2026-01-29T08:50:01.572Z
Learning: In lib/data/provider/container.dart, within the _buildSudoCmd function, do not redirect stderr at the shell level (e.g., 2>/dev/null) when piping the password to sudo -S. Shell-level suppression can swallow stderr before the SSH client receives it, breaking onStderr callbacks in execWithPwd that detect sudo password errors (such as "Sorry, try again.") and Podman emulation messages. Instead, preserve the shell's stderr, receive it via the SSH-level onStderr callback, and filter it from the final result at the application level by using stderr: false in execWithPwd.

Applied to files:

  • lib/data/provider/ai/ai_context.dart
  • lib/data/provider/ai/ai_safety.dart
  • lib/data/provider/ai/ask_ai.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Applies to lib/data/store/**/*.dart : Persist state using Hive stores placed under lib/data/store/

Applied to files:

  • lib/data/store/ai_history.dart
  • lib/data/res/store.dart
📚 Learning: 2025-10-08T09:36:41.682Z
Learnt from: CR
Repo: lollipopkit/flutter_server_box PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-08T09:36:41.682Z
Learning: Use dependency injection via GetIt for services (Stores, Services, etc.)

Applied to files:

  • lib/data/res/store.dart
🔇 Additional comments (24)
lib/data/model/ai/ask_ai_models.dart (1)

29-53: Run build_runner if this model is codegen-backed.

New fields look good. Please confirm whether this model participates in code generation; if yes, regenerate outputs to keep them in sync.

As per coding guidelines: lib/data/model/**/*.dart: After modifying annotated model files, run: dart run build_runner build --delete-conflicting-outputs.

lib/data/model/app/menu/container.dart (1)

5-50: LGTM: askAi menu option is fully wired.

Enum value, menu lists, icon, and label mapping are consistent.

lib/data/provider/ai/ask_ai.dart (4)

212-239: Prompt + context block assembly looks consistent.


265-283: Tool-call schema and parsing updates are consistent.

Also applies to: 357-376


291-335: Scenario prompt mapping and parsing extension look solid.


23-26: All call sites already updated correctly.

The only ask() call site found in the codebase (lib/view/widget/ai/ai_assist_sheet.dart:143) is already updated with both required arguments: scenario and contextBlocks. No further updates needed.

Likely an incorrect or invalid review comment.

lib/view/page/container/container.dart (1)

15-20: Imports align with AI assist integration.

lib/view/page/ssh/page/page.dart (1)

19-28: Imports for AiSafety/AiAssistSheet look fine.

lib/data/store/setting.dart (1)

168-170: Defaults for AI FAB offsets look good.

lib/app.dart (1)

109-115: Manually verify all issue #1038 participants are included in lib/data/res/github_id.dart.

The file was added in this PR with a participants set containing 136 entries. However, without access to issue #1038's full participant data, you must manually confirm that all issue participants (author, assignees, and commenters) are present in the list.

lib/view/page/server/detail/view.dart (1)

128-136: AI entry point looks good.
Localized tooltip + SSH navigation is wired cleanly.

lib/view/page/container/actions.dart (1)

226-249: AI assist context wiring looks solid.
Runtime + item metadata are captured and the open‑SSH apply path is consistent.

lib/data/res/store.dart (1)

3-44: AiHistoryStore is correctly wired into DI and backups.
Getter, backup list, and lazy singleton registration are all in place.

lib/view/page/systemd.dart (1)

34-56: Systemd AI assist action integrates cleanly.
Context blocks + open‑SSH apply path are consistent with the rest of the flow.

lib/data/store/ai_history.dart (1)

1-22: AiHistoryStore implementation looks good.
Simple HiveStore wrapper with a clear history API.

test/ai_safety_test.dart (1)

1-73: AI safety tests are thorough.
Good coverage for redaction variants and risk classification tiers.

lib/view/page/ssh/page/ask_ai.dart (2)

35-52: Good implementation of context block preparation with pre-redaction.

The flow correctly redacts sensitive data via AiSafety.redactBlocks before passing to the AI sheet, then sets redacted: false to prevent double-redaction. This is a sensible pattern.

One minor note: the redacted: false flag name could be confusing since the blocks are redacted—it just means "don't redact again." Consider adding a brief inline comment for future maintainers, though this is optional.


61-69: LGTM on the scrollback tail builder.

The boundary handling with clamp(0, lines.length) is correct, and limiting to 200 lines is reasonable for context size management.

lib/data/provider/ai/ai_safety.dart (2)

116-137: Redaction order matters: short patterns may interfere with compound patterns.

In _redactSpiIdentity, you correctly handle user@ip:port before user@ip before individual components. Good defensive ordering. However, if user or ip are common strings (e.g., user is "root" or ip is "127.0.0.1"), this could over-redact unrelated content. This is acceptable for a safety-first approach.


67-89: Risk classification logic is well-structured.

Clear separation of high-risk destructive patterns vs medium-risk operational patterns. The cascading return pattern is readable and maintainable.

lib/view/widget/ai/ai_assist_sheet.dart (4)

100-115: Good lifecycle management with proper cleanup.

The dispose method correctly cancels the subscription and cleans up controllers. The cascade operator usage for _inputController cleanup is clean.

Minor suggestion: Set _subscription = null after cancel in dispose to be explicit, though not strictly necessary since the widget is being disposed.


215-225: Correct auto-scroll pattern with post-frame callback.

Properly checks hasClients before and after the callback to avoid exceptions when the scroll controller is detached.


263-293: Well-designed confirmation flow with risk-aware countdown.

The countdown button for high-risk or flagged commands provides good UX protection against accidental execution. Uses context.showRoundDialog from fl_lib appropriately.


52-59: LGTM on the public API.

showAiAssistSheet provides a clean entry point with appropriate isScrollControlled and useSafeArea settings for a modal bottom sheet.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


final _rxDdToBlockDevice = RegExp(r'\bdd\b[^\n\r]*\bof\s*=\s*/dev/');

final _rxRmRf = RegExp(r'\brm\b[^\n\r]*\s-[a-z-]*r[a-z-]*f[a-z-]*\b');
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

_rxRmRf pattern misses rm -fr and similar orderings.

The pattern \s-[a-z-]*r[a-z-]*f[a-z-]*\b requires r to appear before f in the flags. Commands like rm -fr /, rm -f -r /, or rm --force -r / won't match. Consider matching both flags independently.

🔧 Suggested fix to match both flag orderings
-final _rxRmRf = RegExp(r'\brm\b[^\n\r]*\s-[a-z-]*r[a-z-]*f[a-z-]*\b');
+// Match rm with both -r/-R and -f/--force in any order
+final _rxRmRf = RegExp(
+  r'\brm\b[^\n\r]*(?=.*\s-[a-z-]*r)(?=.*\s-[a-z-]*f)',
+);

Or use two separate checks in classifyRisk:

// Check for rm with both recursive and force flags
if (_rxRmRecursive.hasMatch(s) && _rxRmForce.hasMatch(s)) return AiCommandRisk.high;
🤖 Prompt for AI Agents
In `@lib/data/provider/ai/ai_safety.dart` at line 166, The _rxRmRf regex only
matches when the 'r' flag appears before 'f' (e.g., "-rf"), so update detection
to handle any flag ordering by either creating two regexes (e.g., _rxRmRecursive
and _rxRmForce) that independently detect recursive and force flags and then
modify classifyRisk to treat a string as high risk when both patterns match, or
change _rxRmRf to a pattern that asserts presence of both flags in any order;
reference _rxRmRf and classifyRisk when applying the fix.

Comment on lines +24 to +36
@override
void didChangeDependencies() {
super.didChangeDependencies();

if (_offsetPx != null) return;

final media = MediaQuery.of(context);
final size = media.size;
final x = Stores.setting.aiFabOffsetX.fetch().clamp(0.0, 1.0);
final y = Stores.setting.aiFabOffsetY.fetch().clamp(0.0, 1.0);

_offsetPx = Offset(size.width * x, size.height * y);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clamp the restored FAB position to bounds.
When screen size or insets change, the initial/restored position can end up off‑screen because it isn’t clamped on load. Consider clamping in didChangeDependencies so the FAB is always visible.

🛠️ Suggested fix
 `@override`
 void didChangeDependencies() {
   super.didChangeDependencies();
 
-  if (_offsetPx != null) return;
-
-  final media = MediaQuery.of(context);
-  final size = media.size;
-  final x = Stores.setting.aiFabOffsetX.fetch().clamp(0.0, 1.0);
-  final y = Stores.setting.aiFabOffsetY.fetch().clamp(0.0, 1.0);
-
-  _offsetPx = Offset(size.width * x, size.height * y);
+  if (_offsetPx == null) {
+    final media = MediaQuery.of(context);
+    final size = media.size;
+    final x = Stores.setting.aiFabOffsetX.fetch().clamp(0.0, 1.0);
+    final y = Stores.setting.aiFabOffsetY.fetch().clamp(0.0, 1.0);
+    _offsetPx = Offset(size.width * x, size.height * y);
+  }
+  _offsetPx = _clampToBounds(_offsetPx!);
 }
🤖 Prompt for AI Agents
In `@lib/view/widget/ai/ai_fab_overlay.dart` around lines 24 - 36, The restored
FAB position in didChangeDependencies can land off-screen; update the logic that
computes _offsetPx to clamp the computed Offset into the visible bounds: obtain
MediaQuery.of(context).size and MediaQuery.of(context).padding (or viewInsets as
needed), compute min/max X and Y allowed (e.g. between left padding and width -
right padding, and top padding and height - bottom padding, optionally
considering the FAB size), then clamp Stores.setting.aiFabOffsetX.fetch() and
aiFabOffsetY.fetch() into [0,1] as before but map them into those constrained
pixel ranges before assigning _offsetPx so the FloatingActionButton stays
on-screen.

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.

proposal: enhance AI functionalities

1 participant