Skip to content

feat: ask ai#936

Merged
lollipopkit merged 6 commits intomainfrom
lollipopkit/issue934
Oct 17, 2025
Merged

feat: ask ai#936
lollipopkit merged 6 commits intomainfrom
lollipopkit/issue934

Conversation

@lollipopkit
Copy link
Copy Markdown
Owner

@lollipopkit lollipopkit commented Oct 17, 2025

Summary by CodeRabbit

  • New Features

    • AI assistant with streaming responses and suggested terminal commands
    • Configurable AI settings: base URL, API key, model selection
    • Terminal toolbar action to open an interactive AI chat with follow-ups and command insertion
    • Multi-language UI text for the AI feature (multiple locales)
  • Dependency Updates

    • Updated terminal/library dependencies for improved compatibility

@lollipopkit lollipopkit linked an issue Oct 17, 2025 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 17, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a streaming AI assistant to the SSH terminal: selection-based queries produce streamed assistant responses and suggested shell commands. Includes AskAi repository and models, settings for base URL/API key/model, UI integration in SSH and Settings, localization additions, dependency updates, and a workflow cache tweak.

Changes

Cohort / File(s) Summary
AI Data Models
lib/data/model/ai/ask_ai_models.dart
New immutable AI data types: AskAiMessageRole, AskAiMessage, AskAiCommand, and sealed AskAiEvent variants (AskAiContentDelta, AskAiToolSuggestion, AskAiCompleted, AskAiStreamError).
AI Repository & Provider
lib/data/provider/ai/ask_ai.dart
New AskAiRepository with streaming ask() method that validates settings, composes a streaming chat request, parses stream chunks into AskAiEvents, aggregates tool calls, and exposes askAiRepositoryProvider. Adds AskAiConfigField, AskAiConfigException, and AskAiNetworkException.
Settings Storage
lib/data/store/setting.dart
Adds askAiBaseUrl, askAiApiKey, and askAiModel properties to SettingStore (defaults: 'https://api.openai.com', '', 'gpt-4o-mini').
Settings UI - AI
lib/view/page/setting/entries/ai.dart, lib/view/page/setting/entry.dart
New AI settings UI: tiles and edit dialogs for base URL, model, and API key; integrated into App settings via new part file.
Settings UI - Controller Refactor
lib/view/page/setting/entries/app.dart, lib/view/page/setting/entries/sftp.dart, lib/view/page/setting/entries/ssh.dart
Refactors several settings onTap handlers to use withTextFieldController wrapper (controller lifecycle moved out of inline async handlers).
SSH Terminal AI Assistant UI
lib/view/page/ssh/page/ask_ai.dart
New AI assistant UI for SSH page: toolbar action, bottom sheet _AskAiSheet, streaming subscription, chat history, command suggestion bubbles, apply/copy command flows, error handling, and auto-scroll/input management (all private/internal).
SSH Page Integration
lib/view/page/ssh/page/page.dart
Adds imports for AI models/provider, includes ask_ai.dart part, and passes toolbarBuilder to TerminalView to enable AI toolbar.
App State change
lib/app.dart
Converts MyApp from StatelessWidget to StatefulWidget; introduces _MyAppState with a late final _introFuture used by FutureBuilder.
Localization Strings
lib/l10n/app_*.arb (all supported locales, e.g., lib/l10n/app_de.arb, lib/l10n/app_en.arb, lib/l10n/app_es.arb, lib/l10n/app_fr.arb, lib/l10n/app_id.arb, lib/l10n/app_ja.arb, lib/l10n/app_nl.arb, lib/l10n/app_pt.arb, lib/l10n/app_ru.arb, lib/l10n/app_tr.arb, lib/l10n/app_uk.arb, lib/l10n/app_zh.arb, lib/l10n/app_zh_tw.arb)
Adds ~16 askAi* localization keys per locale (e.g., askAi, askAiUsageHint, askAiBaseUrl, askAiModel, askAiApiKey, askAiConfigMissing, askAiConfirmExecute, askAiCommandInserted, askAiAwaitingResponse, askAiNoResponse, askAiRecommendedCommand, askAiInsertTerminal, askAiSelectedContent, askAiConversation, askAiFollowUpHint, askAiSend).
Dependencies
pubspec.yaml
Bumps xterm v4.0.4 → v4.0.11 and fl_lib v1.0.355 → v1.0.358.
Workflow
.github/workflows/analysis.yml
Removes .dart_tool/package_config.json from cached paths (only that file no longer cached).

Sequence Diagram

sequenceDiagram
    participant User
    participant SSH as SSH Page / Terminal
    participant Sheet as AI Sheet UI
    participant Repo as AskAi Repository
    participant API as AI Endpoint

    User->>SSH: Select text + tap "Ask AI"
    SSH->>Sheet: Open bottom sheet (with selection)
    User->>Sheet: Send query / follow-up
    Sheet->>Repo: ask(selection, conversation)
    Repo->>Repo: Validate settings (baseUrl, apiKey, model)
    Repo->>API: POST /v1/chat/completions (stream=true)

    loop stream
        API-->>Repo: chunk
        Repo->>Repo: parse chunk (delta / tool_call / done / error)
        alt content delta
            Repo->>Sheet: AskAiContentDelta
            Sheet->>Sheet: append streaming text
        else tool suggestion
            Repo->>Sheet: AskAiToolSuggestion
            Sheet->>Sheet: show suggested command
        else done
            Repo->>Sheet: AskAiCompleted
            Sheet->>Sheet: finalize message & history
        else error
            Repo->>Sheet: AskAiStreamError
            Sheet->>Sheet: show error
        end
    end

    User->>Sheet: tap command action
    Sheet->>SSH: insert or apply command (with optional confirm)
    SSH->>User: command placed/executed in terminal
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Poem

🐰
A rabbit hops where streams now gleam,
It nudges text into an AI dream,
Commands sprinkle like carrot beams,
Settings snug in localized seams,
Now terminals and rabbits scheme!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: ask ai" accurately reflects the primary objective of this pull request, which introduces a comprehensive new AI assistant feature throughout the codebase. The changeset includes new data models for AI interactions (AskAiMessage, AskAiCommand, AskAiEvent variants), a new AskAiRepository for streaming AI responses, configuration settings for the AI service, localization strings across multiple languages, and UI components for the SSH terminal. The title is concise, clear, and specific enough that teammates reviewing git history would immediately understand this adds an AI assistant feature.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 905d525 and 0dd4e6c.

📒 Files selected for processing (2)
  • lib/data/provider/ai/ask_ai.dart (1 hunks)
  • lib/view/page/ssh/page/ask_ai.dart (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
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: 10

Caution

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

⚠️ Outside diff range comments (1)
lib/l10n/app_ru.arb (1)

244-252: Fix typo in Russian translation (contains Chinese character)

“чтобы调整 размер” has a stray Chinese “调整”. Replace with proper Russian.

-  "termFontSizeTip": "Эта настройка повлияет на размер терминала (ширина и высота). Вы можете масштабировать страницу терминала, чтобы调整 размер шрифта текущей сессии.",
+  "termFontSizeTip": "Эта настройка повлияет на размер терминала (ширина и высота). Вы можете масштабировать страницу терминала, чтобы отрегулировать размер шрифта текущей сессии.",
🧹 Nitpick comments (31)
lib/l10n/app_nl.arb (1)

288-299: NL copy nits: improve imperatives.

Small, optional tweaks for natural Dutch:

-  "askAi": "AI vragen",
+  "askAi": "Vraag AI",
-  "askAiConfirmExecute": "Bevestigen voor uitvoeren",
+  "askAiConfirmExecute": "Bevestigen vóór uitvoeren",
lib/l10n/app_pt.arb (1)

287-304: gen-l10n + placeholder metadata for askAiConfigMissing.

  • Run flutter gen-l10n after ARB edits.
  • Add metadata for {fields} to improve generated API clarity.
+  "@askAiConfigMissing": {
+    "description": "Exibido quando faltam configurações obrigatórias de IA.",
+    "placeholders": {
+      "fields": { "type": "String", "example": "URL base / Chave de API / Modelo" }
+    }
+  },

Based on coding guidelines.

lib/l10n/app_uk.arb (2)

287-304: gen-l10n + placeholder metadata for askAiConfigMissing.

  • Run flutter gen-l10n.
  • Add metadata for {fields}.
+  "@askAiConfigMissing": {
+    "description": "Показується, коли відсутні необхідні налаштування ШІ.",
+    "placeholders": {
+      "fields": { "type": "String", "example": "Базова URL-адреса / Ключ API / Модель" }
+    }
+  },

Based on coding guidelines.


290-292: UA copy nit: base URL phrasing.

More natural in UA:

-  "askAiBaseUrl": "Базова URL",
+  "askAiBaseUrl": "Базова URL-адреса",
lib/l10n/app_ja.arb (2)

287-304: gen-l10n + placeholder metadata for askAiConfigMissing.

  • Run flutter gen-l10n.
  • Add metadata for {fields}.
+  "@askAiConfigMissing": {
+    "description": "必須のAI設定が未構成の場合に表示されます。",
+    "placeholders": {
+      "fields": { "type": "String", "example": "ベースURL / APIキー / モデル" }
+    }
+  },

Based on coding guidelines.


291-291: JA typography nit.

Japanese commonly omits the space before “URL”.

-  "askAiBaseUrl": "ベース URL",
+  "askAiBaseUrl": "ベースURL",
lib/l10n/app_zh.arb (2)

287-304: gen-l10n + placeholder metadata for askAiConfigMissing.

  • Run flutter gen-l10n.
  • Add metadata for {fields}.
+  "@askAiConfigMissing": {
+    "description": "必填的 AI 设置缺失时显示。",
+    "placeholders": {
+      "fields": { "type": "String", "example": "基础 URL / API 密钥 / 模型" }
+    }
+  },

Based on coding guidelines.


299-300: ZH copy nit: phrasing for “insert into terminal”.

Reads more naturally with “到”.

-  "askAiInsertTerminal": "插入终端",
+  "askAiInsertTerminal": "插入到终端",
lib/l10n/app_fr.arb (1)

287-304: Run gen-l10n + small copy tweak + placeholder metadata

  • After ARB edits, run: flutter gen-l10n. As per coding guidelines.
  • Replace the visual "|" with natural language to avoid odd rendering.
  • Consider adding placeholders metadata for fields in askAiConfigMissing.

Apply this tweak:

-  "writeScriptTip": "Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l'état du système. Vous pouvez examiner le contenu du script.",
+  "writeScriptTip": "Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` ou `/tmp/server_box` pour surveiller l'état du système. Vous pouvez examiner le contenu du script.",

Please confirm:

  • gen-l10n was re-generated.
  • All locales include the new askAi* keys to prevent missing-string fallbacks. Based on coding guidelines.
lib/data/model/ai/ask_ai_models.dart (1)

26-74: Make value/event types easier to compare and debug

  • Mark leaf classes final to respect the sealed hierarchy.
  • Add ==/hashCode (or use package:equatable) and toString for cleaner state tests/logs.

Example (selected types):

-@immutable
-class AskAiCommand {
+@immutable
+final class AskAiCommand {
   const AskAiCommand({
     required this.command,
     this.description = '',
     this.toolName,
   });
 
   final String command;
   final String description;
   final String? toolName;
+
+  @override
+  String toString() => 'AskAiCommand(command: $command, tool: $toolName)';
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is AskAiCommand &&
+          command == other.command &&
+          description == other.description &&
+          toolName == other.toolName;
+  @override
+  int get hashCode => Object.hash(command, description, toolName);
 }
 
-class AskAiContentDelta extends AskAiEvent {
+final class AskAiContentDelta extends AskAiEvent {
   const AskAiContentDelta(this.delta);
   final String delta;
 }
 
-class AskAiToolSuggestion extends AskAiEvent {
+final class AskAiToolSuggestion extends AskAiEvent {
   const AskAiToolSuggestion(this.command);
   final AskAiCommand command;
 }
 
-class AskAiCompleted extends AskAiEvent {
+final class AskAiCompleted extends AskAiEvent {
   const AskAiCompleted({
     required this.fullText,
     required this.commands,
   });
lib/l10n/app_tr.arb (1)

287-304: Run gen-l10n + small copy tweak + placeholder metadata

  • After ARB edits, run: flutter gen-l10n. As per coding guidelines.
  • Replace the "|" with natural language for clarity.
  • Consider adding placeholders metadata for askAiConfigMissing.

Apply this tweak:

-  "writeScriptTip": "Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.",
+  "writeScriptTip": "Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` veya `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.",

Please confirm gen-l10n was regenerated and all locales received the askAi* keys. Based on coding guidelines.

lib/view/page/setting/entry.dart (1)

124-124: Localize the “AI” section title

Use existing l10n (askAi) instead of a hardcoded string. As per coding guidelines.

-        [const CenterGreyTitle('App'), _buildApp(), const CenterGreyTitle('AI'), _buildAskAiConfig()],
+        [const CenterGreyTitle('App'), _buildApp(), CenterGreyTitle(libL10n.askAi), _buildAskAiConfig()],
lib/view/page/ssh/page/page.dart (2)

249-253: Verify TerminalView.toolbarBuilder support in your xterm version

Ensure the xterm/ui TerminalView you depend on exposes toolbarBuilder with CustomTextEditState; otherwise this will fail at compile/runtime. Gate with isMobile (since showToolbar is mobile-only) or null out builder when unsupported.

Example guard:

-          toolbarBuilder: _buildTerminalToolbar,
+          toolbarBuilder: isMobile ? _buildTerminalToolbar : null,

9-26: Trim + bound selection length before invoking AI

Very long selections can bloat prompts and UI. Consider limiting to N KB (e.g., 8–16 KB) and indicating truncation in the sheet to keep latency/cost predictable.

-  void _showAskAiSheet(String selection) async {
+  void _showAskAiSheet(String selection) async {
+    const max = 16 * 1024; // 16KB
+    final trimmed = selection.trim();
+    final safe = trimmed.length > max ? '${trimmed.substring(0, max)}…' : trimmed;
     ...
-        return _AskAiSheet(selection: selection, localeHint: localeHint, onCommandApply: _applyAiCommand);
+        return _AskAiSheet(selection: safe, localeHint: localeHint, onCommandApply: _applyAiCommand);
lib/view/page/setting/entries/ai.dart (2)

55-94: Add per-field validation (URL format, model non-empty) to dialog

Introduce an optional validator to _showAskAiFieldDialog and validate base URL (https scheme, no trailing spaces) and non-empty model. Prevents misconfig and reduces support churn.

-  Future<void> _showAskAiFieldDialog({
+  Future<void> _showAskAiFieldDialog({
     required HiveProp<String> prop,
     required String title,
     required String hint,
     bool obscure = false,
+    String? Function(String value)? validator,
   }) async {
 ...
-      void onSave() {
-        prop.put(ctrl.text.trim());
+      void onSave() {
+        final v = ctrl.text.trim();
+        final err = validator?.call(v);
+        if (err != null) {
+          context.showSnackBar(err);
+          return;
+        }
+        prop.put(v);
         context.pop();
       }

Usage example for Base URL:

_showAskAiFieldDialog(
  prop: _setting.askAiBaseUrl,
  title: l10n.askAiBaseUrl,
  hint: 'https://api.openai.com',
  validator: (v) {
    final u = Uri.tryParse(v);
    if (u == null || !(u.isScheme('https') || u.isScheme('http')) || u.host.isEmpty) {
      return libL10n.invalid;
    }
    return null;
  },
);

36-49: Offer “Paste” and “Reveal” toggles for API key input

Small UX wins: add a paste shortcut and an eye icon to temporarily reveal the key.

lib/l10n/app_ru.arb (1)

287-304: Add placeholders metadata for askAiConfigMissing and regenerate l10n

Same as DE locale; declare “fields”.

  "askAiConfigMissing": "Настройте {fields} в настройках.",
+  "@askAiConfigMissing": {
+    "placeholders": {
+      "fields": { "type": "String" }
+    }
+  },

As per coding guidelines.

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

363-373: Add a “Stop” action to cancel streaming

Allow users to abort long generations, canceling the subscription and marking idle.

-                  if (_isStreaming)
-                    const SizedBox(height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2)),
+                  if (_isStreaming) ...[
+                    const SizedBox(height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2)),
+                    const SizedBox(width: 8),
+                    IconButton(
+                      tooltip: 'Stop',
+                      icon: const Icon(Icons.stop_circle_outlined),
+                      onPressed: () {
+                        _subscription?.cancel();
+                        setState(() {
+                          _isStreaming = false;
+                          _streamingContent = null;
+                        });
+                      },
+                    ),
+                  ],

183-193: Throttle auto-scroll to reduce jank during rapid deltas

Multiple animateTo calls per frame can stutter. Throttle or use jumpTo when close to bottom.

-  void _scheduleAutoScroll() {
+  void _scheduleAutoScroll() {
     if (!_scrollController.hasClients) return;
     WidgetsBinding.instance.addPostFrameCallback((_) {
       if (!_scrollController.hasClients) return;
-      _scrollController.animateTo(
+      final pos = _scrollController.position;
+      final nearBottom = (pos.maxScrollExtent - pos.pixels) < 80;
+      (nearBottom ? _scrollController.animateTo : _scrollController.jumpTo)(
         _scrollController.position.maxScrollExtent,
         duration: const Duration(milliseconds: 180),
         curve: Curves.easeOutCubic,
       );
     });
   }

295-304: Ensure markdown rendering is safe

If SimpleMarkdown supports HTML, disable raw HTML to avoid accidental rendering of unsafe content from LLMs.

Example:

SimpleMarkdown(
  data: content,
  enableHtml: false, // or equivalent flag in your widget
)

221-235: Keep “insert vs execute” wording consistent

Dialog title says “Confirm execute” but action only inserts text into the terminal. Consider renaming to “Confirm insertion” to avoid confusion.

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

11-13: Use autoDispose for the provider.

This repository is stateless; autoDispose reduces leak risk in short‑lived pages.

-final askAiRepositoryProvider = Provider<AskAiRepository>((ref) {
-  return AskAiRepository();
-});
+final askAiRepositoryProvider =
+    Provider.autoDispose<AskAiRepository>((ref) => AskAiRepository());

42-47: Add SSE-friendly headers and optional language hint.

Improves intermediary behavior and localization.

   final headers = <String, String>{
     Headers.acceptHeader: 'text/event-stream',
     Headers.contentTypeHeader: Headers.jsonContentType,
     'Authorization': authHeader,
+    'Cache-Control': 'no-cache',
   };
+  if (localeHint != null && localeHint.isNotEmpty) {
+    headers['Accept-Language'] = localeHint;
+  }

63-65: SSE streams need longer/no receiveTimeout.

2 minutes can prematurely abort long generations. Use a large value or disable receiveTimeout.

-          receiveTimeout: const Duration(minutes: 2),
+          // Long-lived SSE
+          receiveTimeout: const Duration(hours: 1),

Based on learnings (Dio 5.x timeouts).


85-88: Handle CRLF separators in SSE framing.

Split on blank line with optional CR to avoid stray “\r” in payloads.

-        final segments = carry.toString().split('\n\n');
+        final segments = carry.toString().split(RegExp(r'\r?\n\r?\n'));
         carry
           ..clear()
           ..write(segments.removeLast());

90-101: Assemble multi-line SSE data: events before decoding.

Some servers send multiple data: lines per event. Join them with “\n” before jsonDecode; only then handle [DONE].

// Inside "for (final segment in segments) {"
final dataLines = <String>[];
for (final rawLine in segment.split('\n')) {
  final line = rawLine.trim();
  if (line.startsWith('data:')) {
    dataLines.add(line.substring(5).trim());
  }
}
if (dataLines.isEmpty) continue;

final payload = dataLines.join('\n');
if (payload == '[DONE]') { /* completed */ }
// else jsonDecode(payload)...

196-215: Move system prompt to i18n and neutral fallback.

Hardcoding CN text and CN fallback may surprise non‑CN users. Externalize prompt and default to English when localeHint is absent.


231-258: Consider explicit tool choice.

Some OpenAI‑compatible servers honor tool_choice: 'auto'. If supported, set it explicitly; otherwise skip.

     return {
       'model': model,
       'stream': true,
       'messages': messages,
+      'tool_choice': 'auto',
       'tools': [

261-265: Prefer URI resolve for safer joining.

Handles edge cases (query, base with path) more robustly.

-  Uri _composeUri(String base, String path) {
-    final sanitizedBase = base.replaceAll(RegExp(r'/+$'), '');
-    final sanitizedPath = path.replaceFirst(RegExp(r'^/+'), '');
-    return Uri.parse('$sanitizedBase/$sanitizedPath');
-  }
+  Uri _composeUri(String base, String path) {
+    final baseUri = Uri.parse(base.trim());
+    return baseUri.resolve(path);
+  }

288-293: Clear accumulated arguments after emission.

Prevents accidental re‑parsing if _emitted is reset in future changes.

       _emitted = true;
+      arguments.clear();
       return AskAiCommand(

55-69: Redact Authorization in logs (if LogInterceptor is used).

Ensure debug logging doesn’t leak API keys. Configure a custom LogInterceptor or disable request headers in debug.

Based on learnings (Dio 5.x).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 860c11d and 905d525.

⛔ Files ignored due to path filters (14)
  • lib/generated/l10n/l10n.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_de.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_en.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_es.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_fr.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_id.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_ja.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_nl.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_pt.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_ru.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_tr.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_uk.dart is excluded by !**/generated/**
  • lib/generated/l10n/l10n_zh.dart is excluded by !**/generated/**
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (26)
  • .github/workflows/analysis.yml (0 hunks)
  • lib/app.dart (2 hunks)
  • lib/data/model/ai/ask_ai_models.dart (1 hunks)
  • lib/data/provider/ai/ask_ai.dart (1 hunks)
  • lib/data/store/setting.dart (1 hunks)
  • lib/l10n/app_de.arb (1 hunks)
  • lib/l10n/app_en.arb (1 hunks)
  • lib/l10n/app_es.arb (1 hunks)
  • lib/l10n/app_fr.arb (1 hunks)
  • lib/l10n/app_id.arb (1 hunks)
  • lib/l10n/app_ja.arb (1 hunks)
  • lib/l10n/app_nl.arb (1 hunks)
  • lib/l10n/app_pt.arb (1 hunks)
  • lib/l10n/app_ru.arb (1 hunks)
  • lib/l10n/app_tr.arb (1 hunks)
  • lib/l10n/app_uk.arb (1 hunks)
  • lib/l10n/app_zh.arb (1 hunks)
  • lib/l10n/app_zh_tw.arb (1 hunks)
  • lib/view/page/setting/entries/ai.dart (1 hunks)
  • lib/view/page/setting/entries/app.dart (1 hunks)
  • lib/view/page/setting/entries/sftp.dart (1 hunks)
  • lib/view/page/setting/entries/ssh.dart (1 hunks)
  • lib/view/page/setting/entry.dart (2 hunks)
  • lib/view/page/ssh/page/ask_ai.dart (1 hunks)
  • lib/view/page/ssh/page/page.dart (2 hunks)
  • pubspec.yaml (2 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/analysis.yml
🧰 Additional context used
📓 Path-based instructions (6)
lib/data/store/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • lib/data/store/setting.dart
lib/l10n/**/*.arb

📄 CodeRabbit inference engine (CLAUDE.md)

lib/l10n/**/*.arb: After modifying ARB files, run: flutter gen-l10n to regenerate localization
Keep localized strings in ARB files under lib/l10n/ (Flutter i18n)

Files:

  • lib/l10n/app_id.arb
  • lib/l10n/app_de.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_es.arb
  • lib/l10n/app_zh.arb
  • lib/l10n/app_zh_tw.arb
  • lib/l10n/app_ru.arb
  • lib/l10n/app_uk.arb
  • lib/l10n/app_ja.arb
  • lib/l10n/app_pt.arb
  • lib/l10n/app_tr.arb
  • lib/l10n/app_en.arb
  • lib/l10n/app_nl.arb
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/setting/entries/ssh.dart
  • lib/view/page/setting/entries/sftp.dart
  • lib/view/page/ssh/page/ask_ai.dart
  • lib/view/page/setting/entries/app.dart
  • lib/view/page/setting/entry.dart
  • lib/view/page/ssh/page/page.dart
  • lib/view/page/setting/entries/ai.dart
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
pubspec.yaml

📄 CodeRabbit inference engine (CLAUDE.md)

Use hive_ce instead of hive for Hive integration

Files:

  • pubspec.yaml
lib/data/provider/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • lib/data/provider/ai/ask_ai.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: check
🔇 Additional comments (10)
pubspec.yaml (1)

52-52: LGTM: Dependency updates align with PR objectives.

The xterm and fl_lib version bumps support the new AI-integrated terminal features introduced in this PR.

Also applies to: 68-68

lib/view/page/setting/entries/sftp.dart (1)

47-69: LGTM: Consistent controller lifecycle management.

The refactor to use withTextFieldController removes manual disposal and aligns with the pattern adopted across other setting entries.

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

119-141: LGTM: Improved resource management.

The migration to withTextFieldController ensures consistent controller lifecycle management across SSH settings.

lib/l10n/app_en.arb (1)

287-311: LGTM: Complete AI localization with proper metadata.

The English locale includes all necessary AI-related strings with correct placeholder declarations.

lib/app.dart (2)

14-23: LGTM: Proper FutureBuilder pattern.

The migration to StatefulWidget with a cached _introFuture prevents the future from being recreated on every build, which is the correct pattern for FutureBuilder usage.


101-101: LGTM: Future reference correctly updated.

The FutureBuilder now references the state-held _introFuture instead of calling _IntroPage.builders directly.

lib/data/model/ai/ask_ai_models.dart (1)

3-24: Solid, minimal message model

Enum + apiRole mapping looks good; const/immutable applied correctly. No codegen required here.

lib/view/page/setting/entry.dart (1)

38-38: Part linkage is correct

Verified: entry.dart line 38 correctly declares part 'entries/ai.dart'; and ai.dart line 1 contains the matching part of '../entry.dart'; declaration. The relative path correctly resolves from the entries subdirectory to the parent entry.dart file.

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

145-149: Review suggestion references non-functional pattern; underlying security concern is valid but fix is incorrect.

The security risk is real: askAiApiKey stores sensitive data in plaintext Hive. However, the proposed SecureProp('askAiApiKey') fix doesn't match this codebase's actual secure storage pattern.

Evidence:

  • Backup password uses SecureProp('bakPasswd') but is never referenced—the actual implementation uses SecureStoreProps.bakPwd.read()/.write() (async).
  • SecureProp appears to be an unused pattern from the fl_lib dependency.
  • The active secure storage mechanism is SecureStoreProps with async read/write, incompatible with askAiApiKey's current synchronous .fetch() usage.

The fix would require async refactoring across ask_ai.dart and callers, which the suggested diff doesn't address.

Likely an incorrect or invalid review comment.

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

146-151: Tool-calls indexing: guard against missing/duplicate indices.

If provider omits index or reorders, defaulting to 0 can merge different calls. Prefer a stable key (id if present) or allocate per appearance.

Would the target API always include tool_calls[].index? If not, consider:

final key = (toolCall['id'] as String?) ?? (toolCall['index']?.toString() ?? '${toolBuilders.length}');
final builder = toolBuilders.putIfAbsent(key.hashCode, _ToolCallBuilder.new);

Comment on lines +55 to +69
Response<ResponseBody> response;
try {
response = await _dio.postUri<ResponseBody>(
uri,
data: jsonEncode(requestBody),
options: Options(
responseType: ResponseType.stream,
headers: headers,
sendTimeout: const Duration(seconds: 20),
receiveTimeout: const Duration(minutes: 2),
),
);
} on DioException catch (e) {
throw AskAiNetworkException(message: e.message ?? 'Request failed', cause: e);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Wire request cancellation to stream cancellation (avoid hangs/leaks).

With async*, subscriber cancellation won’t cancel the HTTP request. Tie a CancelToken to onCancel via a StreamController.

// Replace the async* implementation with this pattern.
Stream<AskAiEvent> ask({
  required String selection,
  String? localeHint,
  List<AskAiMessage> conversation = const [],
}) {
  final cancelToken = CancelToken();
  final controller = StreamController<AskAiEvent>(
    onCancel: () => cancelToken.cancel('ask() cancelled by listener'),
  );

  () async {
    try {
      // ...config checks...

      final uri = _composeUri(baseUrl, '/v1/chat/completions');
      final response = await _dio.postUri<ResponseBody>(
        uri,
        data: jsonEncode(requestBody),
        options: Options(
          responseType: ResponseType.stream,
          headers: headers,
          sendTimeout: const Duration(seconds: 20),
          receiveTimeout: const Duration(hours: 1),
        ),
        cancelToken: cancelToken,
      );

      // ...same SSE decode loop, but use controller.add(...) instead of yield...
      // On completion:
      // controller.add(AskAiCompleted(...));
    } catch (e, s) {
      controller.add(AskAiStreamError(e, s));
    } finally {
      await controller.close();
    }
  }();

  return controller.stream;
}

As a minimal step (still insufficient alone), pass a CancelToken now:

-      response = await _dio.postUri<ResponseBody>(
+      final cancelToken = CancelToken();
+      response = await _dio.postUri<ResponseBody>(
         uri,
         data: jsonEncode(requestBody),
         options: Options(
           responseType: ResponseType.stream,
           headers: headers,
           sendTimeout: const Duration(seconds: 20),
-          receiveTimeout: const Duration(minutes: 2),
+          receiveTimeout: const Duration(hours: 1),
         ),
-      );
+        cancelToken: cancelToken,
+      );

Based on learnings (Dio 5.x).

Comment on lines +287 to +304
"writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.",
"askAi": "KI fragen",
"askAiUsageHint": "Verwendet im SSH-Terminal",
"askAiBaseUrl": "Basis-URL",
"askAiModel": "Modell",
"askAiApiKey": "API-Schlüssel",
"askAiConfigMissing": "Bitte konfigurieren Sie {fields} in den Einstellungen.",
"askAiConfirmExecute": "Vor Ausführung bestätigen",
"askAiCommandInserted": "Befehl ins Terminal eingefügt",
"askAiAwaitingResponse": "Warte auf KI-Antwort...",
"askAiNoResponse": "Keine Antwort",
"askAiRecommendedCommand": "KI-empfohlener Befehl",
"askAiInsertTerminal": "In Terminal einfügen",
"askAiSelectedContent": "Ausgewählter Inhalt",
"askAiConversation": "KI-Unterhaltung",
"askAiFollowUpHint": "Weitere Frage stellen...",
"askAiSend": "Senden"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Add placeholders metadata for askAiConfigMissing and regenerate l10n

Define the “fields” placeholder to keep gen-l10n strict and consistent across locales, then run flutter gen-l10n.

   "askAiConfigMissing": "Bitte konfigurieren Sie {fields} in den Einstellungen.",
+  "@askAiConfigMissing": {
+    "placeholders": {
+      "fields": { "type": "String" }
+    }
+  },

As per coding guidelines.


🏁 Script executed:

#!/bin/bash
# Verify all locales define askAiConfigMissing placeholder metadata
rg -n '"askAiConfigMissing":' lib/l10n/*.arb -C2
rg -n '"@askAiConfigMissing"' lib/l10n/*.arb
# Regenerate localizations (run locally)
# flutter gen-l10n

Length of output: 4571


Add placeholder metadata for askAiConfigMissing to all locale files and regenerate l10n

Verification confirms only app_en.arb (line 288) defines @askAiConfigMissing metadata, while all other 12 locale files—including app_de.arb—lack the placeholder definition despite using {fields} consistently. Add the metadata block to every non-English locale and run flutter gen-l10n per coding guidelines.

   "askAiConfigMissing": "Bitte konfigurieren Sie {fields} in den Einstellungen.",
+  "@askAiConfigMissing": {
+    "placeholders": {
+      "fields": { "type": "String" }
+    }
+  },

Apply to: lib/l10n/app_de.arb, lib/l10n/app_id.arb, lib/l10n/app_fr.arb, lib/l10n/app_es.arb, lib/l10n/app_ja.arb, lib/l10n/app_pt.arb, lib/l10n/app_nl.arb, lib/l10n/app_ru.arb, lib/l10n/app_tr.arb, lib/l10n/app_uk.arb, lib/l10n/app_zh.arb, lib/l10n/app_zh_tw.arb.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.",
"askAi": "KI fragen",
"askAiUsageHint": "Verwendet im SSH-Terminal",
"askAiBaseUrl": "Basis-URL",
"askAiModel": "Modell",
"askAiApiKey": "API-Schlüssel",
"askAiConfigMissing": "Bitte konfigurieren Sie {fields} in den Einstellungen.",
"askAiConfirmExecute": "Vor Ausführung bestätigen",
"askAiCommandInserted": "Befehl ins Terminal eingefügt",
"askAiAwaitingResponse": "Warte auf KI-Antwort...",
"askAiNoResponse": "Keine Antwort",
"askAiRecommendedCommand": "KI-empfohlener Befehl",
"askAiInsertTerminal": "In Terminal einfügen",
"askAiSelectedContent": "Ausgewählter Inhalt",
"askAiConversation": "KI-Unterhaltung",
"askAiFollowUpHint": "Weitere Frage stellen...",
"askAiSend": "Senden"
}
"writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.",
"askAi": "KI fragen",
"askAiUsageHint": "Verwendet im SSH-Terminal",
"askAiBaseUrl": "Basis-URL",
"askAiModel": "Modell",
"askAiApiKey": "API-Schlüssel",
"askAiConfigMissing": "Bitte konfigurieren Sie {fields} in den Einstellungen.",
"@askAiConfigMissing": {
"placeholders": {
"fields": { "type": "String" }
}
},
"askAiConfirmExecute": "Vor Ausführung bestätigen",
"askAiCommandInserted": "Befehl ins Terminal eingefügt",
"askAiAwaitingResponse": "Warte auf KI-Antwort...",
"askAiNoResponse": "Keine Antwort",
"askAiRecommendedCommand": "KI-empfohlener Befehl",
"askAiInsertTerminal": "In Terminal einfügen",
"askAiSelectedContent": "Ausgewählter Inhalt",
"askAiConversation": "KI-Unterhaltung",
"askAiFollowUpHint": "Weitere Frage stellen...",
"askAiSend": "Senden"
}
🤖 Prompt for AI Agents
In lib/l10n/app_de.arb around lines 287 to 304, the translation uses the
placeholder {fields} for "askAiConfigMissing" but lacks the corresponding
metadata block; add an @askAiConfigMissing entry immediately after the
"askAiConfigMissing" key with a JSON object specifying "description" and
"placeholders" -> "fields" -> {"type":"String"} (matching app_en.arb), repeat
the same metadata structure in each listed non-English ARB file, then run
`flutter gen-l10n` to regenerate localization code.

Comment on lines +288 to +304
"askAi": "Preguntar a la IA",
"askAiUsageHint": "Usado en el terminal SSH",
"askAiBaseUrl": "URL base",
"askAiModel": "Modelo",
"askAiApiKey": "Clave API",
"askAiConfigMissing": "Configura {fields} en Ajustes.",
"askAiConfirmExecute": "Confirmar antes de ejecutar",
"askAiCommandInserted": "Comando insertado en el terminal",
"askAiAwaitingResponse": "Esperando la respuesta de la IA...",
"askAiNoResponse": "Sin respuesta",
"askAiRecommendedCommand": "Comando sugerido por la IA",
"askAiInsertTerminal": "Insertar en el terminal",
"askAiSelectedContent": "Contenido seleccionado",
"askAiConversation": "Conversación con la IA",
"askAiFollowUpHint": "Haz una pregunta adicional...",
"askAiSend": "Enviar"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing placeholder metadata for askAiConfigMissing.

The English locale (app_en.arb lines 288-294) includes placeholder metadata for askAiConfigMissing:

"@askAiConfigMissing": {
  "placeholders": {
    "fields": {
      "type": "String"
    }
  }
}

This metadata is missing in the Spanish locale, which may cause issues with Flutter's localization generator when the {fields} placeholder is used.

Apply this diff to add the missing metadata:

   "writeScriptTip": "Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script.",
+  "@askAiConfigMissing": {
+    "placeholders": {
+      "fields": {
+        "type": "String"
+      }
+    }
+  },
   "askAi": "Preguntar a la IA",
🤖 Prompt for AI Agents
In lib/l10n/app_es.arb around lines 288 to 304, the key "askAiConfigMissing"
includes a {fields} placeholder but lacks the corresponding metadata block; add
an "@askAiConfigMissing" entry with a placeholders object containing "fields"
with type "String" (matching the English app_en.arb metadata) so Flutter's
localization generator recognizes the placeholder.

Comment on lines +288 to +304
"askAi": "Tanya AI",
"askAiUsageHint": "Digunakan di Terminal SSH",
"askAiBaseUrl": "URL dasar",
"askAiModel": "Model",
"askAiApiKey": "Kunci API",
"askAiConfigMissing": "Harap konfigurasikan {fields} di Pengaturan.",
"askAiConfirmExecute": "Konfirmasi sebelum menjalankan",
"askAiCommandInserted": "Perintah dimasukkan ke terminal",
"askAiAwaitingResponse": "Menunggu respons AI...",
"askAiNoResponse": "Tidak ada respons",
"askAiRecommendedCommand": "Perintah yang disarankan AI",
"askAiInsertTerminal": "Masukkan ke terminal",
"askAiSelectedContent": "Konten yang dipilih",
"askAiConversation": "Percakapan AI",
"askAiFollowUpHint": "Ajukan pertanyaan lanjutan...",
"askAiSend": "Kirim"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing placeholder metadata for askAiConfigMissing.

The Indonesian locale is also missing the placeholder metadata for askAiConfigMissing. This metadata is required for proper localization generation when the {fields} placeholder is used.

Apply this diff to add the missing metadata:

   "writeScriptTip": "Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut.",
+  "@askAiConfigMissing": {
+    "placeholders": {
+      "fields": {
+        "type": "String"
+      }
+    }
+  },
   "askAi": "Tanya AI",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"askAi": "Tanya AI",
"askAiUsageHint": "Digunakan di Terminal SSH",
"askAiBaseUrl": "URL dasar",
"askAiModel": "Model",
"askAiApiKey": "Kunci API",
"askAiConfigMissing": "Harap konfigurasikan {fields} di Pengaturan.",
"askAiConfirmExecute": "Konfirmasi sebelum menjalankan",
"askAiCommandInserted": "Perintah dimasukkan ke terminal",
"askAiAwaitingResponse": "Menunggu respons AI...",
"askAiNoResponse": "Tidak ada respons",
"askAiRecommendedCommand": "Perintah yang disarankan AI",
"askAiInsertTerminal": "Masukkan ke terminal",
"askAiSelectedContent": "Konten yang dipilih",
"askAiConversation": "Percakapan AI",
"askAiFollowUpHint": "Ajukan pertanyaan lanjutan...",
"askAiSend": "Kirim"
}
"askAiConfigMissing": "Harap konfigurasikan {fields} di Pengaturan.",
"@askAiConfigMissing": {
"placeholders": {
"fields": {
"type": "String"
}
}
},
"askAi": "Tanya AI",
"askAiUsageHint": "Digunakan di Terminal SSH",
"askAiBaseUrl": "URL dasar",
"askAiModel": "Model",
"askAiApiKey": "Kunci API",
"askAiConfirmExecute": "Konfirmasi sebelum menjalankan",
"askAiCommandInserted": "Perintah dimasukkan ke terminal",
"askAiAwaitingResponse": "Menunggu respons AI...",
"askAiNoResponse": "Tidak ada respons",
"askAiRecommendedCommand": "Perintah yang disarankan AI",
"askAiInsertTerminal": "Masukkan ke terminal",
"askAiSelectedContent": "Konten yang dipilih",
"askAiConversation": "Percakapan AI",
"askAiFollowUpHint": "Ajukan pertanyaan lanjutan...",
"askAiSend": "Kirim"
}
🤖 Prompt for AI Agents
In lib/l10n/app_id.arb around lines 288 to 304, the key "askAiConfigMissing"
uses the {fields} placeholder but lacks the required ARB metadata entry; add a
JSON metadata object named "@askAiConfigMissing" directly after the string entry
that includes a brief description and a placeholders map defining "fields" (e.g.
{"description":"Error shown when AI config fields are
missing","placeholders":{"fields":{}}}) so the localization tooling can
recognize the {fields} placeholder.

Comment on lines +287 to +304
"writeScriptTip": "Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren.",
"askAi": "AI vragen",
"askAiUsageHint": "Gebruikt in de SSH-terminal",
"askAiBaseUrl": "Basis-URL",
"askAiModel": "Model",
"askAiApiKey": "API-sleutel",
"askAiConfigMissing": "Configureer {fields} in de instellingen.",
"askAiConfirmExecute": "Bevestigen voor uitvoeren",
"askAiCommandInserted": "Commando in terminal ingevoegd",
"askAiAwaitingResponse": "Wachten op AI-reactie...",
"askAiNoResponse": "Geen reactie",
"askAiRecommendedCommand": "Door AI voorgestelde opdracht",
"askAiInsertTerminal": "In terminal invoegen",
"askAiSelectedContent": "Geselecteerde inhoud",
"askAiConversation": "AI-gesprek",
"askAiFollowUpHint": "Stel een vervolgvraag...",
"askAiSend": "Verzenden"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Run gen-l10n and add placeholder metadata for askAiConfigMissing.

  • Please run: flutter gen-l10n after ARB edits. As per coding guidelines.
  • Add metadata for the {fields} placeholder to keep generated API types clear.

Apply this minimal addition near askAiConfigMissing:

+  "@askAiConfigMissing": {
+    "description": "Shown when required AI settings are missing.",
+    "placeholders": {
+      "fields": { "type": "String", "example": "Base URL / API Key / Model" }
+    }
+  },

Optionally verify ARB consistency across locales:

Based on coding guidelines.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Validate JSON
for f in lib/l10n/*.arb; do
  jq -e . "$f" >/dev/null || { echo "Invalid JSON: $f"; exit 1; }
done

# 2) Expected AI keys
expected='["askAi","askAiUsageHint","askAiBaseUrl","askAiModel","askAiApiKey","askAiConfigMissing","askAiConfirmExecute","askAiCommandInserted","askAiAwaitingResponse","askAiNoResponse","askAiRecommendedCommand","askAiInsertTerminal","askAiSelectedContent","askAiConversation","askAiFollowUpHint","askAiSend"]'

# 3) Check presence and duplicates
for f in lib/l10n/*.arb; do
  fileKeys=$(jq -r 'keys[]' "$f" | grep '^askAi' || true)
  # Missing keys
  missing=$(jq -nc --argfile exp <(echo "$expected") --argfile got <(printf '%s\n' "$fileKeys" | jq -R . | jq -s .) \
    '$exp - $got')
  if [ "$(echo "$missing" | jq 'length')" -ne 0 ]; then
    echo "Missing in $f: $(echo "$missing" | jq -r '.[]' | paste -sd, -)"
  fi
  # Duplicates (textual)
  dups=$(rg -nP '^\s*"askAi[A-Za-z]*"\s*:' "$f" | cut -d: -f2 | sort | uniq -d || true)
  [ -n "$dups" ] && echo "Possible duplicate keys in $f: $dups"
  # Metadata presence for askAiConfigMissing
  if ! jq -e 'has("@askAiConfigMissing")' "$f" >/dev/null; then
    echo "Note: add @askAiConfigMissing metadata in $f"
  fi
done

echo "ARB checks complete."

Length of output: 94


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Step 1: Validate JSON in all ARB files ==="
for f in lib/l10n/*.arb; do
  if ! jq -e . "$f" >/dev/null 2>&1; then
    echo "Invalid JSON: $f"
    exit 1
  fi
  echo "✓ Valid JSON: $f"
done

echo ""
echo "=== Step 2: Check for @askAiConfigMissing metadata ==="
for f in lib/l10n/*.arb; do
  if jq -e 'has("@askAiConfigMissing")' "$f" >/dev/null 2>&1; then
    echo "✓ Found @askAiConfigMissing in $f"
  else
    echo "✗ Missing @askAiConfigMissing in $f"
  fi
done

echo ""
echo "=== Step 3: List all askAi* keys in app_nl.arb ==="
jq -r 'keys[] | select(startswith("askAi")) | sort' lib/l10n/app_nl.arb

echo ""
echo "=== Step 4: Check for {fields} placeholder in askAiConfigMissing value ==="
if jq -e '.askAiConfigMissing | contains("{fields}")' lib/l10n/app_nl.arb >/dev/null 2>&1; then
  echo "✓ askAiConfigMissing contains {fields} placeholder"
  jq -r '.askAiConfigMissing' lib/l10n/app_nl.arb
else
  echo "✗ askAiConfigMissing does NOT contain {fields} placeholder"
fi

echo ""
echo "ARB checks complete."

Length of output: 1386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Step 1: List all askAi* keys in app_nl.arb (no sort) ==="
jq -r 'keys[] | select(startswith("askAi"))' lib/l10n/app_nl.arb | sort

echo ""
echo "=== Step 2: Extract @askAiConfigMissing metadata from app_en.arb (template) ==="
jq '.["@askAiConfigMissing"]' lib/l10n/app_en.arb

echo ""
echo "=== Step 3: Check for {fields} placeholder in app_nl.arb askAiConfigMissing value ==="
jq -r '.askAiConfigMissing // "KEY NOT FOUND"' lib/l10n/app_nl.arb

echo ""
echo "=== Step 4: Verify all expected AI keys in app_nl.arb ==="
expected_keys=("askAi" "askAiUsageHint" "askAiBaseUrl" "askAiModel" "askAiApiKey" "askAiConfigMissing" "askAiConfirmExecute" "askAiCommandInserted" "askAiAwaitingResponse" "askAiNoResponse" "askAiRecommendedCommand" "askAiInsertTerminal" "askAiSelectedContent" "askAiConversation" "askAiFollowUpHint" "askAiSend")

for key in "${expected_keys[@]}"; do
  if jq -e ".\"$key\"" lib/l10n/app_nl.arb >/dev/null 2>&1; then
    echo "$key"
  else
    echo "$key MISSING"
  fi
done

echo ""
echo "Verification complete."

Length of output: 989


Add @askAiConfigMissing metadata to app_nl.arb and regenerate localization files.

The @askAiConfigMissing metadata is missing from app_nl.arb (lines 287–304). Metadata exists only in app_en.arb; all 12 other locales also lack it. Add the metadata block and run flutter gen-l10n to regenerate generated types.

+  "@askAiConfigMissing": {
+    "description": "Shown when required AI settings are missing.",
+    "placeholders": {
+      "fields": {
+        "type": "String"
+      }
+    }
+  },

Apply the same metadata addition to all other ARB files: app_de.arb, app_es.arb, app_fr.arb, app_id.arb, app_ja.arb, app_pt.arb, app_ru.arb, app_tr.arb, app_uk.arb, app_zh.arb, app_zh_tw.arb.

Then run: flutter gen-l10n

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +288 to +304
"askAi": "詢問 AI",
"askAiUsageHint": "於 SSH 終端機中使用",
"askAiBaseUrl": "基礎 URL",
"askAiModel": "模型",
"askAiApiKey": "API 金鑰",
"askAiConfigMissing": "請前往設定配置 {fields}",
"askAiConfirmExecute": "執行前確認",
"askAiCommandInserted": "指令已插入終端機",
"askAiAwaitingResponse": "等待 AI 回應...",
"askAiNoResponse": "無回覆內容",
"askAiRecommendedCommand": "AI 推薦指令",
"askAiInsertTerminal": "插入終端機",
"askAiSelectedContent": "選取的內容",
"askAiConversation": "AI 對話",
"askAiFollowUpHint": "繼續提問...",
"askAiSend": "傳送"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing placeholder metadata for askAiConfigMissing.

Similar to the Spanish locale, the Traditional Chinese locale is missing the placeholder metadata for askAiConfigMissing. The English locale (app_en.arb lines 288-294) shows the required structure.

Apply this diff to add the missing metadata:

   "writeScriptTip": "連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。",
+  "@askAiConfigMissing": {
+    "placeholders": {
+      "fields": {
+        "type": "String"
+      }
+    }
+  },
   "askAi": "詢問 AI",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"askAi": "詢問 AI",
"askAiUsageHint": "於 SSH 終端機中使用",
"askAiBaseUrl": "基礎 URL",
"askAiModel": "模型",
"askAiApiKey": "API 金鑰",
"askAiConfigMissing": "請前往設定配置 {fields}",
"askAiConfirmExecute": "執行前確認",
"askAiCommandInserted": "指令已插入終端機",
"askAiAwaitingResponse": "等待 AI 回應...",
"askAiNoResponse": "無回覆內容",
"askAiRecommendedCommand": "AI 推薦指令",
"askAiInsertTerminal": "插入終端機",
"askAiSelectedContent": "選取的內容",
"askAiConversation": "AI 對話",
"askAiFollowUpHint": "繼續提問...",
"askAiSend": "傳送"
}
"askAiModel": "模型",
"askAiApiKey": "API 金鑰",
"@askAiConfigMissing": {
"placeholders": {
"fields": {
"type": "String"
}
}
},
"askAiConfigMissing": "請前往設定配置 {fields}",
"askAiConfirmExecute": "執行前確認",
"askAiCommandInserted": "指令已插入終端機",
"askAiAwaitingResponse": "等待 AI 回應...",
"askAiNoResponse": "無回覆內容",
"askAiRecommendedCommand": "AI 推薦指令",
"askAiInsertTerminal": "插入終端機",
"askAiSelectedContent": "選取的內容",
"askAiConversation": "AI 對話",
"askAiFollowUpHint": "繼續提問...",
"askAiSend": "傳送"
}
🤖 Prompt for AI Agents
In lib/l10n/app_zh_tw.arb around lines 288 to 304, the message key
"askAiConfigMissing" is missing its placeholder metadata; add the corresponding
metadata entry for "@askAiConfigMissing" (matching the structure used in
app_en.arb) that declares the "fields" placeholder (an object with
"type":"String" or empty object as in English) and any description so the
placeholder is recognized by tooling and translations.

Comment on lines +36 to +49
_setting.askAiApiKey.listenable().listenVal((val) {
final hasKey = val.isNotEmpty;
return ListTile(
leading: const Icon(MingCute.key_2_line),
title: Text(l10n.askAiApiKey),
subtitle: Text(hasKey ? '••••••••' : libL10n.empty, style: UIs.textGrey),
onTap: () => _showAskAiFieldDialog(
prop: _setting.askAiApiKey,
title: l10n.askAiApiKey,
hint: 'sk-...',
obscure: true,
),
);
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid persisting API keys in Hive; use secure storage

askAiApiKey is stored as plain text. Use platform secure storage (Keychain/Keystore via flutter_secure_storage) and gate desktop with OS vaults or at least encryption-at-rest. Also exclude from backups if possible.

Minimal approach:

  • Replace HiveProp for API key with a SecureStorage-backed repo.
  • Keep Hive only for baseUrl/model.
    I can draft a storage adapter if helpful.
🤖 Prompt for AI Agents
lib/view/page/setting/entries/ai.dart lines 36-49: The UI currently reads/writes
askAiApiKey from a Hive-backed HiveProp and displays/stores the API key as plain
text; replace persistence with a secure-storage-backed adapter. Implement a
SecureStorageRepository (e.g., using flutter_secure_storage on mobile and OS
vaults on desktop) that exposes async get/set/delete for the API key, keep Hive
for non-sensitive fields (baseUrl/model) only, update the dialog and ListTile to
read the key via the secure adapter (await get) and write via set/delete, ensure
the storage is encrypted/OS-vaulted, mark desktop to use platform vault or
encrypted file, and exclude backups where supported; update the prop usage to
call the adapter instead of HiveProp and handle async state in the widget (show
masked value when present).

Comment on lines +95 to 126
onTap: () {
withTextFieldController((ctrl) async {
await context.showRoundDialog(
title: libL10n.primaryColorSeed,
child: StatefulBuilder(
builder: (context, setState) {
final children = <Widget>[
/// Plugin [dynamic_color] is not supported on iOS
if (!isIOS)
ListTile(
title: Text(l10n.followSystem),
trailing: StoreSwitch(
prop: _setting.useSystemPrimaryColor,
callback: (_) => setState(() {}),
),
),
),
];
if (!_setting.useSystemPrimaryColor.fetch()) {
children.add(
ColorPicker(
color: Color(_setting.colorSeed.fetch()),
onColorChanged: (c) => ctrl.text = c.toHex,
),
);
}
return Column(mainAxisSize: MainAxisSize.min, children: children);
},
),
actions: Btn.ok(onTap: () => _onSaveColor(ctrl.text)).toList,
);
ctrl.dispose();
];
if (!_setting.useSystemPrimaryColor.fetch()) {
children.add(
ColorPicker(
color: Color(_setting.colorSeed.fetch()),
onColorChanged: (c) => ctrl.text = c.toHex,
),
);
}
return Column(mainAxisSize: MainAxisSize.min, children: children);
},
),
actions: Btn.ok(onTap: () => _onSaveColor(ctrl.text)).toList,
);
});
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Don’t require color save when “Follow system” is ON

OK currently calls _onSaveColor(ctrl.text) even if useSystemPrimaryColor is enabled, causing a false “fail” snackbar. Close the dialog without parsing color in that case.

-            actions: Btn.ok(onTap: () => _onSaveColor(ctrl.text)).toList,
+            actions: Btn.ok(onTap: () {
+              if (_setting.useSystemPrimaryColor.fetch()) {
+                context.pop(); // nothing to save
+              } else {
+                _onSaveColor(ctrl.text);
+              }
+            }).toList,

As per coding guidelines.

🤖 Prompt for AI Agents
In lib/view/page/setting/entries/app.dart around lines 95 to 126, the OK action
always calls _onSaveColor(ctrl.text) even when useSystemPrimaryColor is enabled
causing a false failure snackbar; change the OK callback to first check
_setting.useSystemPrimaryColor.fetch() and if it is true simply pop/close the
dialog (or return) without attempting to parse or save the color, otherwise call
_onSaveColor(ctrl.text) as before; update Btn.ok(onTap: ...) to perform that
conditional check so the dialog closes cleanly when “Follow system” is ON.

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.

Copy-Paste Repair

1 participant