Conversation
|
Caution Review failedThe pull request is closed. WalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
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. Comment |
There was a problem hiding this comment.
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 titleUse 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 versionEnsure 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 AIVery 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 dialogIntroduce 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 inputSmall 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 l10nSame 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 streamingAllow 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 deltasMultiple 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 safeIf 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 consistentDialog 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 SSEdata: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
_emittedis 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
⛔ Files ignored due to path filters (14)
lib/generated/l10n/l10n.dartis excluded by!**/generated/**lib/generated/l10n/l10n_de.dartis excluded by!**/generated/**lib/generated/l10n/l10n_en.dartis excluded by!**/generated/**lib/generated/l10n/l10n_es.dartis excluded by!**/generated/**lib/generated/l10n/l10n_fr.dartis excluded by!**/generated/**lib/generated/l10n/l10n_id.dartis excluded by!**/generated/**lib/generated/l10n/l10n_ja.dartis excluded by!**/generated/**lib/generated/l10n/l10n_nl.dartis excluded by!**/generated/**lib/generated/l10n/l10n_pt.dartis excluded by!**/generated/**lib/generated/l10n/l10n_ru.dartis excluded by!**/generated/**lib/generated/l10n/l10n_tr.dartis excluded by!**/generated/**lib/generated/l10n/l10n_uk.dartis excluded by!**/generated/**lib/generated/l10n/l10n_zh.dartis excluded by!**/generated/**pubspec.lockis 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.arblib/l10n/app_de.arblib/l10n/app_fr.arblib/l10n/app_es.arblib/l10n/app_zh.arblib/l10n/app_zh_tw.arblib/l10n/app_ru.arblib/l10n/app_uk.arblib/l10n/app_ja.arblib/l10n/app_pt.arblib/l10n/app_tr.arblib/l10n/app_en.arblib/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.dartlib/view/page/setting/entries/sftp.dartlib/view/page/ssh/page/ask_ai.dartlib/view/page/setting/entries/app.dartlib/view/page/setting/entry.dartlib/view/page/ssh/page/page.dartlib/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
withTextFieldControllerremoves 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
withTextFieldControllerensures 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
_introFutureprevents 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
_introFutureinstead of calling_IntroPage.buildersdirectly.lib/data/model/ai/ask_ai_models.dart (1)
3-24: Solid, minimal message modelEnum + apiRole mapping looks good; const/immutable applied correctly. No codegen required here.
lib/view/page/setting/entry.dart (1)
38-38: Part linkage is correctVerified:
entry.dartline 38 correctly declarespart 'entries/ai.dart';andai.dartline 1 contains the matchingpart 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:
askAiApiKeystores sensitive data in plaintext Hive. However, the proposedSecureProp('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 usesSecureStoreProps.bakPwd.read()/.write()(async).SecurePropappears to be an unused pattern from the fl_lib dependency.- The active secure storage mechanism is
SecureStorePropswith async read/write, incompatible withaskAiApiKey's current synchronous.fetch()usage.The fix would require async refactoring across
ask_ai.dartand 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);
| 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); | ||
| } |
There was a problem hiding this comment.
🛠️ 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).
| "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" | ||
| } |
There was a problem hiding this comment.
🛠️ 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-l10nLength 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.
| "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.
| "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" | ||
| } |
There was a problem hiding this comment.
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.
| "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" | ||
| } |
There was a problem hiding this comment.
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.
| "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.
| "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" | ||
| } |
There was a problem hiding this comment.
🧩 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.
| "askAi": "詢問 AI", | ||
| "askAiUsageHint": "於 SSH 終端機中使用", | ||
| "askAiBaseUrl": "基礎 URL", | ||
| "askAiModel": "模型", | ||
| "askAiApiKey": "API 金鑰", | ||
| "askAiConfigMissing": "請前往設定配置 {fields}", | ||
| "askAiConfirmExecute": "執行前確認", | ||
| "askAiCommandInserted": "指令已插入終端機", | ||
| "askAiAwaitingResponse": "等待 AI 回應...", | ||
| "askAiNoResponse": "無回覆內容", | ||
| "askAiRecommendedCommand": "AI 推薦指令", | ||
| "askAiInsertTerminal": "插入終端機", | ||
| "askAiSelectedContent": "選取的內容", | ||
| "askAiConversation": "AI 對話", | ||
| "askAiFollowUpHint": "繼續提問...", | ||
| "askAiSend": "傳送" | ||
| } |
There was a problem hiding this comment.
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.
| "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.
| _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, | ||
| ), | ||
| ); | ||
| }), |
There was a problem hiding this comment.
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).
| 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, | ||
| ); | ||
| }); | ||
| }, |
There was a problem hiding this comment.
🛠️ 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.
Summary by CodeRabbit
New Features
Dependency Updates