// ignore_for_file: unused_import, unnecessary_import import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math' as math; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:markdown/markdown.dart' as md; import 'package:path_provider/path_provider.dart'; import '../../app/app_controller.dart'; import '../../app/app_metadata.dart'; import '../../app/ui_feature_manifest.dart'; import '../../i18n/app_language.dart'; import '../../models/app_models.dart'; import '../../runtime/runtime_models.dart'; import '../../theme/app_palette.dart'; import '../../theme/app_theme.dart'; import '../../widgets/assistant_focus_panel.dart'; import '../../widgets/assistant_artifact_sidebar.dart'; import '../../widgets/desktop_workspace_scaffold.dart'; import '../../widgets/pane_resize_handle.dart'; import '../../widgets/surface_card.dart'; import 'assistant_page_main.dart'; import 'assistant_page_components.dart'; import 'assistant_page_composer_bar.dart'; import 'assistant_page_composer_state_helpers.dart'; import 'assistant_page_composer_support.dart'; import 'assistant_page_tooltip_labels.dart'; import 'assistant_page_message_widgets.dart'; import 'assistant_page_composer_skill_picker.dart'; import 'assistant_page_composer_clipboard.dart'; import 'assistant_page_components_core.dart'; enum BubbleToneInternal { user, assistant, agent } enum TimelineItemKindInternal { user, assistant, agent, toolCall } class TimelineItemInternal { const TimelineItemInternal._({ required this.key, required this.kind, this.label, this.text, this.title, this.pending = false, this.error = false, }); const TimelineItemInternal.message({ required String key, required TimelineItemKindInternal kind, required String label, required String text, required bool pending, required bool error, }) : this._( key: key, kind: kind, label: label, text: text, pending: pending, error: error, ); const TimelineItemInternal.toolCall({ required String key, required String toolName, required String summary, required bool pending, required bool error, }) : this._( key: key, kind: TimelineItemKindInternal.toolCall, title: toolName, text: summary, pending: pending, error: error, ); final String key; final TimelineItemKindInternal kind; final String? label; final String? text; final String? title; final bool pending; final bool error; } class AssistantTaskSeedInternal { const AssistantTaskSeedInternal({ required this.sessionKey, required this.title, required this.preview, required this.status, required this.updatedAtMs, required this.owner, required this.surface, required this.executionTarget, required this.draft, }); final String sessionKey; final String title; final String preview; final String status; final double updatedAtMs; final String owner; final String surface; final AssistantExecutionTarget executionTarget; final bool draft; AssistantTaskEntryInternal toEntry({required bool isCurrent}) { return AssistantTaskEntryInternal( sessionKey: sessionKey, title: title, preview: preview, status: status, updatedAtMs: updatedAtMs, owner: owner, surface: surface, executionTarget: executionTarget, isCurrent: isCurrent, draft: draft, ); } } class AssistantTaskEntryInternal { const AssistantTaskEntryInternal({ required this.sessionKey, required this.title, required this.preview, required this.status, required this.updatedAtMs, required this.owner, required this.surface, required this.executionTarget, required this.isCurrent, this.draft = false, }); final String sessionKey; final String title; final String preview; final String status; final double? updatedAtMs; final String owner; final String surface; final AssistantExecutionTarget executionTarget; final bool isCurrent; final bool draft; AssistantTaskEntryInternal copyWith({ String? sessionKey, String? title, String? preview, String? status, double? updatedAtMs, String? owner, String? surface, AssistantExecutionTarget? executionTarget, bool? isCurrent, bool? draft, }) { return AssistantTaskEntryInternal( sessionKey: sessionKey ?? this.sessionKey, title: title ?? this.title, preview: preview ?? this.preview, status: status ?? this.status, updatedAtMs: updatedAtMs ?? this.updatedAtMs, owner: owner ?? this.owner, surface: surface ?? this.surface, executionTarget: executionTarget ?? this.executionTarget, isCurrent: isCurrent ?? this.isCurrent, draft: draft ?? this.draft, ); } String get updatedAtLabel => sessionUpdatedAtLabelInternal(updatedAtMs); } class AssistantTaskGroupInternal { const AssistantTaskGroupInternal({ required this.executionTarget, required this.items, }); final AssistantExecutionTarget executionTarget; final List items; } class PillStyleInternal { const PillStyleInternal({ required this.backgroundColor, required this.foregroundColor, }); final Color backgroundColor; final Color foregroundColor; } class MetaPillInternal extends StatelessWidget { const MetaPillInternal({super.key, required this.label, required this.icon}); final String label; final IconData icon; @override Widget build(BuildContext context) { final palette = context.palette; final theme = Theme.of(context); return LayoutBuilder( builder: (context, constraints) { final maxWidth = constraints.maxWidth; if (maxWidth.isFinite && maxWidth < 20) { return const SizedBox.shrink(); } final showText = !maxWidth.isFinite || maxWidth >= 52; final horizontalPadding = showText ? 10.0 : 8.0; return Container( padding: EdgeInsets.symmetric( horizontal: horizontalPadding, vertical: 6, ), decoration: BoxDecoration( color: palette.surfaceSecondary, borderRadius: BorderRadius.circular(999), border: Border.all(color: palette.strokeSoft), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: palette.textMuted), if (showText) ...[ const SizedBox(width: 6), Flexible( child: Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.labelMedium?.copyWith( color: palette.textSecondary, ), ), ), ], ], ), ); }, ); } } PillStyleInternal pillStyleForStatusInternal( BuildContext context, String label, ) { final theme = Theme.of(context); final normalized = normalizedTaskStatusInternal(label); return switch (normalized) { 'running' => PillStyleInternal( backgroundColor: context.palette.accentMuted, foregroundColor: theme.colorScheme.primary, ), 'queued' => PillStyleInternal( backgroundColor: context.palette.surfaceSecondary, foregroundColor: context.palette.textSecondary, ), 'interrupted' || 'failed' || 'error' => PillStyleInternal( backgroundColor: context.palette.surfacePrimary, foregroundColor: theme.colorScheme.error, ), _ => PillStyleInternal( backgroundColor: context.palette.surfacePrimary, foregroundColor: theme.colorScheme.tertiary, ), }; } String normalizedTaskStatusInternal(String status) { final value = status.trim().toLowerCase(); return switch (value) { 'running' => 'running', 'interrupted' => 'interrupted', 'queued' => 'queued', 'failed' => 'failed', 'error' => 'error', 'open' => 'open', _ => 'open', }; } String toolCallStatusLabelInternal(String status) => switch (normalizedTaskStatusInternal(status)) { 'running' => appText('运行中', 'Running'), 'interrupted' => appText('已中断', 'Interrupted'), 'failed' || 'error' => appText('错误', 'Error'), _ => appText('已完成', 'Completed'), }; String assistantThinkingLabelInternal(String level) => switch (level) { 'low' => appText('低', 'Low'), 'medium' => appText('中', 'Medium'), 'max' => appText('超高', 'Max'), _ => appText('高', 'High'), }; String sessionDisplayTitleInternal(GatewaySessionSummary session) { final label = session.label.trim(); if (label.isEmpty || label == session.key) { return fallbackSessionTitleInternal(session.key); } return label; } String fallbackSessionTitleInternal(String sessionKey) { final trimmed = sessionKey.trim(); if (trimmed.startsWith('draft:')) { return appText('新对话', 'New conversation'); } return trimmed.isEmpty ? appText('未命名对话', 'Untitled conversation') : trimmed; } String? sessionPreviewInternal(GatewaySessionSummary session) { final preview = session.lastMessagePreview?.trim(); if (preview != null && preview.isNotEmpty) { return preview; } final subject = session.subject?.trim(); if (subject != null && subject.isNotEmpty) { return subject; } return null; } String sessionStatusInternal( GatewaySessionSummary session, { required bool sessionPending, String lifecycleStatus = '', }) { final normalizedLifecycle = normalizedTaskStatusInternal(lifecycleStatus); if (session.abortedLastRun == true) { return 'failed'; } if (normalizedLifecycle == 'queued') { return 'queued'; } if (sessionPending) { return 'running'; } if (normalizedLifecycle == 'interrupted') { return 'interrupted'; } if ((session.lastMessagePreview ?? '').trim().isEmpty) { return 'queued'; } return 'open'; } String sessionUpdatedAtLabelInternal(double? updatedAtMs) { if (updatedAtMs == null) { return appText('未知', 'Unknown'); } final delta = DateTime.now().difference( DateTime.fromMillisecondsSinceEpoch(updatedAtMs.toInt()), ); if (delta.inMinutes < 1) { return appText('刚刚', 'Now'); } if (delta.inHours < 1) { return '${delta.inMinutes}m'; } if (delta.inDays < 1) { return '${delta.inHours}h'; } return '${delta.inDays}d'; } double estimatedComposerWrapSectionHeightInternal({ required int itemCount, required double availableWidth, required double averageChipWidth, }) { if (itemCount <= 0) { return 0; } final itemsPerRow = math.max(1, (availableWidth / averageChipWidth).floor()); final rows = (itemCount / itemsPerRow).ceil(); const chipHeight = 32.0; const runSpacing = 6.0; const sectionSpacing = 6.0; return sectionSpacing + (rows * chipHeight) + ((rows - 1) * runSpacing); } bool sessionKeysMatchInternal(String incoming, String current) { final left = incoming.trim().toLowerCase(); final right = current.trim().toLowerCase(); return left == right; }