xworkmate-app/lib/features/settings/settings_page_presentation.dart

281 lines
10 KiB
Dart

// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'package:flutter/material.dart';
import '../../app/app_controller.dart';
import '../../app/app_metadata.dart';
import '../../app/app_store_policy.dart';
import '../../app/ui_feature_manifest.dart';
import '../../app/workspace_navigation.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/gateway_runtime.dart';
import '../../runtime/runtime_controllers.dart';
import '../../runtime/runtime_models.dart';
import 'codex_integration_card.dart';
import 'skill_directory_authorization_card.dart';
import '../../widgets/section_tabs.dart';
import '../../widgets/surface_card.dart';
import '../../widgets/top_bar.dart';
import 'settings_page_core.dart';
import 'settings_page_sections.dart';
import 'settings_page_gateway.dart';
import 'settings_page_gateway_connection.dart';
import 'settings_page_gateway_llm.dart';
import 'settings_page_multi_agent.dart';
import 'settings_page_support.dart';
import 'settings_page_device.dart';
import 'settings_page_widgets.dart';
extension SettingsPagePresentationMixinInternal on SettingsPageStateInternal {
List<Widget> buildAppearanceInternal(
BuildContext context,
AppController controller,
) {
return [
SurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('主题', 'Theme'),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
ChoiceChip(
label: Text(appText('浅色', 'Light')),
selected: controller.themeMode == ThemeMode.light,
onSelected: (_) => controller.setThemeMode(ThemeMode.light),
),
ChoiceChip(
label: Text(appText('深色', 'Dark')),
selected: controller.themeMode == ThemeMode.dark,
onSelected: (_) => controller.setThemeMode(ThemeMode.dark),
),
ChoiceChip(
label: Text(appText('跟随系统', 'System')),
selected: controller.themeMode == ThemeMode.system,
onSelected: (_) => controller.setThemeMode(ThemeMode.system),
),
],
),
],
),
),
];
}
List<Widget> buildDiagnosticsInternal(
BuildContext context,
AppController controller,
) {
final runtimeLogs = controller.runtimeLogs
.where(matchesRuntimeLogFilterInternal)
.toList(growable: false)
.reversed
.toList(growable: false);
return [
SurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('网关诊断', 'Gateway Diagnostics'),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
InfoRowInternal(
label: appText('连接', 'Connection'),
value: controller.connection.status.label,
),
InfoRowInternal(
label: appText('地址', 'Address'),
value:
controller.connection.remoteAddress ??
appText('离线', 'Offline'),
),
InfoRowInternal(
label: appText('代理', 'Agent'),
value: controller.activeAgentName,
),
InfoRowInternal(
label: appText('认证模式', 'Auth Mode'),
value:
controller.connection.connectAuthMode ??
appText('未发起', 'Not attempted'),
),
InfoRowInternal(
label: appText('认证诊断', 'Auth Diagnostics'),
value: controller.connection.connectAuthSummary,
),
InfoRowInternal(
label: appText('健康负载', 'Health Payload'),
value: controller.connection.healthPayload == null
? appText('不可用', 'Unavailable')
: encodePrettyJson(controller.connection.healthPayload!),
),
InfoRowInternal(
label: appText('状态负载', 'Status Payload'),
value: controller.connection.statusPayload == null
? appText('不可用', 'Unavailable')
: encodePrettyJson(controller.connection.statusPayload!),
),
],
),
),
const SizedBox(height: 16),
SurfaceCard(
key: const ValueKey('runtime-log-card'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('运行日志', 'Runtime Logs'),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 6),
Text(
appText(
'只记录本机运行期的连接、鉴权、配对和 socket 诊断,不写入密钥明文。',
'Shows local runtime diagnostics for connection, auth, pairing, and socket events without logging secret values.',
),
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
const SizedBox(width: 12),
OutlinedButton(
onPressed: runtimeLogs.isEmpty
? null
: () => controller.clearRuntimeLogs(),
child: Text(appText('清空', 'Clear')),
),
],
),
const SizedBox(height: 16),
TextField(
key: const ValueKey('runtime-log-filter'),
controller: runtimeLogFilterControllerInternal,
decoration: InputDecoration(
labelText: appText('筛选日志', 'Filter Logs'),
hintText: appText(
'按级别、分类或关键字过滤',
'Filter by level, category, or keyword',
),
prefixIcon: const Icon(Icons.manage_search_rounded),
),
onChanged: (_) => setStateInternal(() {}),
),
const SizedBox(height: 16),
if (runtimeLogs.isEmpty)
Text(
appText('当前没有运行日志。', 'No runtime logs yet.'),
style: Theme.of(context).textTheme.bodyMedium,
)
else
Container(
constraints: const BoxConstraints(maxHeight: 320),
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
),
child: SelectionArea(
child: ListView.separated(
itemCount: runtimeLogs.length,
shrinkWrap: true,
itemBuilder: (context, index) {
final entry = runtimeLogs[index];
return SelectableText(
entry.line,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontFamily: 'monospace',
),
);
},
separatorBuilder: (context, index) =>
const SizedBox(height: 8),
),
),
),
],
),
),
const SizedBox(height: 16),
SurfaceCard(
key: const ValueKey('assistant-local-state-card'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('本地数据清理', 'Local Data Cleanup'),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
appText(
'删除本机保存的 Assistant 任务线程会话、本地设置快照和恢复备份,不会删除已保存密钥,也不会触碰外部 Codex 全局目录。',
'Deletes locally saved Assistant threads, settings snapshots, and recovery backups. Stored secrets and the external Codex home stay untouched.',
),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
Align(
alignment: Alignment.centerLeft,
child: FilledButton.tonalIcon(
key: const ValueKey('assistant-local-state-clear-button'),
onPressed: () => showClearAssistantLocalStateDialogInternal(
context,
controller,
),
icon: const Icon(Icons.delete_forever_rounded),
label: Text(
appText('清理任务线程与本地配置', 'Clear threads and local config'),
),
),
),
],
),
),
const SizedBox(height: 16),
SurfaceCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appText('设备', 'Device'),
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
InfoRowInternal(
label: appText('平台', 'Platform'),
value: controller.runtime.deviceInfo.platformLabel,
),
InfoRowInternal(
label: appText('设备类型', 'Device Family'),
value: controller.runtime.deviceInfo.deviceFamily,
),
InfoRowInternal(
label: appText('型号标识', 'Model Identifier'),
value: controller.runtime.deviceInfo.modelIdentifier,
),
],
),
),
];
}
}