Skip to content

Commit 5d365ed

Browse files
committed
Version 1.0.3 is expected to be released.
1 parent eea13a5 commit 5d365ed

15 files changed

Lines changed: 169 additions & 114 deletions

lib/core/agent/agent_context.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ class AgentContextBuilder {
294294
// contextWindow 为 0 时,ContextCompactor 用 128K 兜底
295295
contextWindow: modelCard.contextWindow,
296296
),
297-
])
297+
]),
298298
);
299299
}
300300

@@ -349,7 +349,8 @@ class AgentContextBuilder {
349349
'path': skill.path,
350350
'tool_count': skill.toolCount,
351351
'staging_cleaned': cleaned,
352-
'message': '技能「${skill.name}」已安装到全局技能库,可在技能页管理并在任意工作目录复用'
352+
'message':
353+
'技能「${skill.name}」已安装到全局技能库,可在技能页管理并在任意工作目录复用'
353354
'${cleaned ? "(工作目录的临时副本已清理)" : ""}。',
354355
});
355356
} catch (e) {

lib/core/agent/transformers/context_compactor.dart

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,7 @@ class ContextCompactor extends MessageTransformer {
125125
// ─── 第三步:重组消息列表 ───
126126
final result = <Map<String, dynamic>>[
127127
system,
128-
{
129-
'role': 'system',
130-
'content': '[以下是之前对话的摘要,供你参考上下文]\n$summary',
131-
},
128+
{'role': 'system', 'content': '[以下是之前对话的摘要,供你参考上下文]\n$summary'},
132129
...recent,
133130
];
134131

@@ -207,9 +204,7 @@ class ContextCompactor extends MessageTransformer {
207204
/// 每 3 轮对话为一组,调用 LLM 提取值得记忆的信息并存储。
208205
/// 这确保了即使摘要被再次压缩,重要信息仍可通过语义检索召回。
209206
Future<void> _sinkToMemory(List<Map<String, dynamic>> messages) async {
210-
AppLogger.instance.log(
211-
'[Compactor] 开始记忆沉淀: ${messages.length}条消息',
212-
);
207+
AppLogger.instance.log('[Compactor] 开始记忆沉淀: ${messages.length}条消息');
213208

214209
// 按 6 条为一组(约 3 轮对话)批量处理
215210
const batchSize = 6;
@@ -266,9 +261,7 @@ class ContextCompactor extends MessageTransformer {
266261
Future<String> _summarize(List<Map<String, dynamic>> messages) async {
267262
final text = _messagesToText(messages);
268263

269-
AppLogger.instance.log(
270-
'[Compactor] 生成摘要: 原文 ${text.length} 字符',
271-
);
264+
AppLogger.instance.log('[Compactor] 生成摘要: 原文 ${text.length} 字符');
272265

273266
try {
274267
final response = await llm.chat([
@@ -288,9 +281,7 @@ class ContextCompactor extends MessageTransformer {
288281
]);
289282

290283
final summary = response.content?.trim() ?? '';
291-
AppLogger.instance.log(
292-
'[Compactor] 摘要生成完成: ${summary.length} 字符',
293-
);
284+
AppLogger.instance.log('[Compactor] 摘要生成完成: ${summary.length} 字符');
294285
return summary.isEmpty ? _fallbackSummary(messages) : summary;
295286
} catch (e) {
296287
AppLogger.instance.log('[Compactor] 摘要生成失败: $e, 使用降级方案');
@@ -307,7 +298,9 @@ class ContextCompactor extends MessageTransformer {
307298
final content = msg['content'];
308299
final text = content is String ? content : '';
309300
if (text.isNotEmpty) {
310-
final preview = text.length > 80 ? '${text.substring(0, 80)}...' : text;
301+
final preview = text.length > 80
302+
? '${text.substring(0, 80)}...'
303+
: text;
311304
lines.add('[$role] $preview');
312305
}
313306
}

lib/core/skill/skill_registry.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ class SkillRegistry {
102102

103103
DateTime installedAt = DateTime.now();
104104
try {
105-
installedAt = await skillMdFile
106-
.lastModified()
107-
.timeout(const Duration(seconds: 2));
105+
installedAt = await skillMdFile.lastModified().timeout(
106+
const Duration(seconds: 2),
107+
);
108108
} catch (_) {}
109109

110110
skills.add(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'dart:async';
2+
3+
import 'package:file_selector/file_selector.dart' as fs;
4+
5+
/// 统一的目录选择封装。
6+
///
7+
/// 历史问题:`file_picker` 的目录选择(底层 comdlg32)在 Windows 上会**原生崩溃**,
8+
/// 且无 Dart 异常(try/catch 接不住、日志里看不到任何痕迹)。改用 `file_selector`
9+
/// (底层 IFileDialog)稳定。全应用所有"选目录"入口都应走此函数,便于统一维护。
10+
///
11+
/// [dialogTitle] 仅用于语义保留;`file_selector`[confirmButtonText] 作为确认按钮文案,
12+
/// 不支持设置标题栏文字,这里把标题忽略(或作为确认按钮文案的回退)。
13+
/// [initialDirectory] 打开对话框时的初始目录。
14+
/// [timeout] 防止原生对话框卡死的兜底超时(默认 60s)。
15+
///
16+
/// 用户取消、超时或任何异常都返回 `null`,绝不抛出——调用方据此静默处理。
17+
Future<String?> pickDirectory({
18+
String? dialogTitle,
19+
String? initialDirectory,
20+
Duration timeout = const Duration(seconds: 60),
21+
}) async {
22+
try {
23+
final path = await fs
24+
.getDirectoryPath(initialDirectory: initialDirectory)
25+
.timeout(timeout);
26+
return path;
27+
} on TimeoutException {
28+
// 原生对话框卡死的兜底,静默忽略
29+
return null;
30+
} catch (_) {
31+
// 其他平台异常,静默忽略
32+
return null;
33+
}
34+
}

lib/features/chat/chat_page.dart

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:path/path.dart' as p;
1111

1212
import '../../core/export/conversation_exporter.dart';
1313
import '../../core/l10n/l10n_ext.dart';
14+
import '../../core/utils/directory_picker.dart';
1415
import '../../core/llm/models.dart';
1516
import '../../core/models/file_attachment.dart';
1617
import '../../core/settings/app_settings.dart';
@@ -1446,18 +1447,10 @@ class _WorkDirChip extends ConsumerWidget {
14461447
await ref.read(settingsProvider.notifier).updateWorkingDirectory('');
14471448
}
14481449

1449-
try {
1450-
final dir = await FilePicker.platform
1451-
.getDirectoryPath(dialogTitle: title)
1452-
.timeout(const Duration(seconds: 60));
1453-
if (dir != null && context.mounted) {
1454-
ref.read(workingDirectoryProvider.notifier).state = dir;
1455-
await ref.read(settingsProvider.notifier).updateWorkingDirectory(dir);
1456-
}
1457-
} on TimeoutException {
1458-
// 文件对话框超时(系统级卡死),静默忽略
1459-
} catch (_) {
1460-
// 其他平台异常,静默忽略
1450+
final dir = await pickDirectory(dialogTitle: title);
1451+
if (dir != null && context.mounted) {
1452+
ref.read(workingDirectoryProvider.notifier).state = dir;
1453+
await ref.read(settingsProvider.notifier).updateWorkingDirectory(dir);
14611454
}
14621455
}
14631456

lib/features/chat/slash_commands.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ SlashParseResult parseSlashCommand(String rawText) {
8989
for (final cmd in kSlashCommands) {
9090
final isExact = text == cmd.command;
9191
final hasArg =
92-
text.startsWith('${cmd.command} ') || text.startsWith('${cmd.command}\n');
92+
text.startsWith('${cmd.command} ') ||
93+
text.startsWith('${cmd.command}\n');
9394
if (isExact || hasArg) {
9495
final description = text.substring(cmd.command.length).trim();
9596
if (description.isEmpty && cmd.requiresDescription) {

lib/features/chat/widgets/new_workspace_dialog.dart

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
import 'dart:async';
21
import 'dart:convert';
32
import 'dart:io';
43

54
import 'package:dio/dio.dart';
6-
import 'package:file_picker/file_picker.dart';
75
import 'package:flutter/material.dart';
86
import 'package:flutter_riverpod/flutter_riverpod.dart';
97
import 'package:path/path.dart' as p;
108

119
import '../../../core/l10n/l10n_ext.dart';
10+
import '../../../core/utils/directory_picker.dart';
1211
import '../../../core/settings/app_settings.dart';
1312
import '../../../providers/settings_provider.dart';
1413
import '../chat_provider.dart';
@@ -455,16 +454,12 @@ class _NewWorkspaceDialogState extends ConsumerState<NewWorkspaceDialog> {
455454
if (_parentDir.isNotEmpty && !Directory(_parentDir).existsSync()) {
456455
setState(() => _parentDir = '');
457456
}
458-
try {
459-
final dir = await FilePicker.platform
460-
.getDirectoryPath(dialogTitle: context.s.wsDialogSelectParentTitle)
461-
.timeout(const Duration(seconds: 60));
462-
if (dir != null) {
463-
setState(() => _parentDir = dir);
464-
}
465-
} on TimeoutException {
466-
// 系统文件对话框超时
467-
} catch (_) {}
457+
final dir = await pickDirectory(
458+
dialogTitle: context.s.wsDialogSelectParentTitle,
459+
);
460+
if (dir != null && mounted) {
461+
setState(() => _parentDir = dir);
462+
}
468463
}
469464

470465
Future<void> _runTest(EmbeddingConfig? cfg) async {

lib/features/models/model_cards_page.dart

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,28 @@ class ModelCardsPage extends ConsumerWidget {
9898
showDialog(
9999
context: context,
100100
builder: (ctx) => _ModelCardDialog(
101-
onSave: (name, baseUrl, apiKey, modelId, logoPath, provider, contextWindow) {
102-
ref
103-
.read(modelCardsProvider.notifier)
104-
.addCard(
105-
name: name,
106-
baseUrl: baseUrl,
107-
apiKey: apiKey,
108-
modelId: modelId,
109-
logoPath: logoPath,
110-
provider: provider,
111-
contextWindow: contextWindow,
112-
);
113-
},
101+
onSave:
102+
(
103+
name,
104+
baseUrl,
105+
apiKey,
106+
modelId,
107+
logoPath,
108+
provider,
109+
contextWindow,
110+
) {
111+
ref
112+
.read(modelCardsProvider.notifier)
113+
.addCard(
114+
name: name,
115+
baseUrl: baseUrl,
116+
apiKey: apiKey,
117+
modelId: modelId,
118+
logoPath: logoPath,
119+
provider: provider,
120+
contextWindow: contextWindow,
121+
);
122+
},
114123
),
115124
);
116125
}
@@ -313,18 +322,27 @@ class _ModelCardTile extends ConsumerWidget {
313322
initialModelId: card.modelId,
314323
initialLogoPath: card.logoPath,
315324
initialProvider: card.provider,
316-
onSave: (name, baseUrl, apiKey, modelId, logoPath, provider, contextWindow) {
317-
final updated = card.copyWith(
318-
name: name,
319-
baseUrl: baseUrl,
320-
apiKey: apiKey,
321-
modelId: modelId,
322-
logoPath: logoPath,
323-
provider: provider,
324-
contextWindow: contextWindow,
325-
);
326-
ref.read(modelCardsProvider.notifier).updateCard(updated);
327-
},
325+
onSave:
326+
(
327+
name,
328+
baseUrl,
329+
apiKey,
330+
modelId,
331+
logoPath,
332+
provider,
333+
contextWindow,
334+
) {
335+
final updated = card.copyWith(
336+
name: name,
337+
baseUrl: baseUrl,
338+
apiKey: apiKey,
339+
modelId: modelId,
340+
logoPath: logoPath,
341+
provider: provider,
342+
contextWindow: contextWindow,
343+
);
344+
ref.read(modelCardsProvider.notifier).updateCard(updated);
345+
},
328346
),
329347
);
330348
}

lib/features/multi_agent/widgets/agent_workspace.dart

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import 'dart:async';
21
import 'dart:io';
32

43
import 'package:flutter/material.dart';
54
import 'package:flutter_riverpod/flutter_riverpod.dart';
65
import 'package:dock_panel/dock_panel.dart';
76
import 'package:uuid/uuid.dart';
8-
import 'package:file_picker/file_picker.dart';
97

108
import '../../../core/l10n/l10n_ext.dart';
9+
import '../../../core/utils/directory_picker.dart';
1110
import '../models/agent_config.dart';
1211
import '../providers/multi_agent_provider.dart';
1312
import '../theme/agent_dock_theme.dart';
@@ -261,16 +260,10 @@ class _WorkspaceSetup extends StatelessWidget {
261260
const SizedBox(height: 28),
262261
FilledButton.icon(
263262
onPressed: () async {
264-
try {
265-
final dir = await FilePicker.platform
266-
.getDirectoryPath(
267-
dialogTitle: context.s.multiAgentSelectDirTitle,
268-
)
269-
.timeout(const Duration(seconds: 60));
270-
if (dir != null) onSelected(dir);
271-
} on TimeoutException {
272-
// 系统文件对话框超时
273-
} catch (_) {}
263+
final dir = await pickDirectory(
264+
dialogTitle: context.s.multiAgentSelectDirTitle,
265+
);
266+
if (dir != null) onSelected(dir);
274267
},
275268
icon: const Icon(Icons.folder, size: 20),
276269
label: Text(context.s.multiAgentOpenDir),

lib/features/settings/settings_page.dart

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import 'dart:async';
2-
31
import 'package:flutter/material.dart';
42
import 'package:flutter_riverpod/flutter_riverpod.dart';
53
import 'package:file_picker/file_picker.dart';
64
import 'package:url_launcher/url_launcher.dart';
75
import '../../core/l10n/l10n_ext.dart';
6+
import '../../core/utils/directory_picker.dart';
87
import '../../l10n/app_localizations.dart';
98
import '../../providers/settings_provider.dart';
109
import '../../providers/skills_provider.dart';
@@ -121,16 +120,7 @@ class SettingsPage extends ConsumerWidget {
121120
}
122121

123122
Future<void> _pickHistoryPath(BuildContext context, WidgetRef ref) async {
124-
final String? result;
125-
try {
126-
result = await FilePicker.platform
127-
.getDirectoryPath(dialogTitle: '选择历史记录保存目录')
128-
.timeout(const Duration(seconds: 60));
129-
} on TimeoutException {
130-
return;
131-
} catch (_) {
132-
return;
133-
}
123+
final result = await pickDirectory(dialogTitle: '选择历史记录保存目录');
134124
if (result == null) return;
135125
if (!context.mounted) return;
136126

@@ -143,16 +133,7 @@ class SettingsPage extends ConsumerWidget {
143133
}
144134

145135
Future<void> _pickSkillsPath(BuildContext context, WidgetRef ref) async {
146-
final String? result;
147-
try {
148-
result = await FilePicker.platform
149-
.getDirectoryPath(dialogTitle: '选择技能存放目录')
150-
.timeout(const Duration(seconds: 60));
151-
} on TimeoutException {
152-
return;
153-
} catch (_) {
154-
return;
155-
}
136+
final result = await pickDirectory(dialogTitle: '选择技能存放目录');
156137
if (result == null) return;
157138
if (!context.mounted) return;
158139

@@ -167,16 +148,7 @@ class SettingsPage extends ConsumerWidget {
167148
}
168149

169150
Future<void> _pickLogsPath(BuildContext context, WidgetRef ref) async {
170-
final String? result;
171-
try {
172-
result = await FilePicker.platform
173-
.getDirectoryPath(dialogTitle: '选择日志存放目录')
174-
.timeout(const Duration(seconds: 60));
175-
} on TimeoutException {
176-
return;
177-
} catch (_) {
178-
return;
179-
}
151+
final result = await pickDirectory(dialogTitle: '选择日志存放目录');
180152
if (result == null) return;
181153
if (!context.mounted) return;
182154

0 commit comments

Comments
 (0)