Simplify skill list items to names only

This commit is contained in:
Haitao Pan 2026-03-26 18:53:20 +08:00
parent 041fb8869e
commit cb8b11fe5b
2 changed files with 159 additions and 250 deletions

View File

@ -279,56 +279,14 @@ class _SkillListTile extends StatelessWidget {
]
: const [],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
skill.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 10),
StatusBadge(
status: skill.disabled
? _skillStatus(
appText('已禁用', 'Disabled'),
StatusTone.warning,
)
: _skillStatus(
appText('已启用', 'Enabled'),
StatusTone.success,
),
),
],
),
const SizedBox(height: 8),
Text(
skill.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: palette.textSecondary,
height: 1.4,
),
),
const SizedBox(height: 10),
Wrap(
spacing: 10,
runSpacing: 6,
children: [
_SkillMeta(label: skill.source),
_SkillMeta(label: skill.primaryEnv ?? 'workspace'),
],
),
],
child: Text(
skill.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
color: selected ? palette.textPrimary : null,
),
),
),
),
@ -518,21 +476,5 @@ class _DependencyCard extends StatelessWidget {
}
}
class _SkillMeta extends StatelessWidget {
const _SkillMeta({required this.label});
final String label;
@override
Widget build(BuildContext context) {
return Text(
label,
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: context.palette.textMuted),
);
}
}
StatusInfo _skillStatus(String label, StatusTone tone) =>
StatusInfo(label, tone);

View File

@ -185,7 +185,10 @@ class _WebTasksPageState extends State<WebTasksPage> {
},
),
),
Container(width: 1, color: context.palette.strokeSoft),
Container(
width: 1,
color: context.palette.strokeSoft,
),
Expanded(
child: _TaskDetailPanel(
controller: controller,
@ -259,7 +262,9 @@ class _WebSkillsPageState extends State<WebSkillsPage> {
animation: widget.controller,
builder: (context, _) {
final controller = widget.controller;
final skills = controller.skills.where(_matchesQuery).toList(growable: false);
final skills = controller.skills
.where(_matchesQuery)
.toList(growable: false);
final selected = _resolveSelectedSkill(skills);
return DesktopWorkspaceScaffold(
breadcrumbs: _buildWebBreadcrumbs(
@ -409,9 +414,9 @@ class _WebNodesPageState extends State<WebNodesPage> {
animation: widget.controller,
builder: (context, _) {
final controller = widget.controller;
final items = _itemsForTab(controller)
.where(_matchesQuery)
.toList(growable: false);
final items = _itemsForTab(
controller,
).where(_matchesQuery).toList(growable: false);
final selected = _resolveSelected(items);
return DesktopWorkspaceScaffold(
breadcrumbs: _buildWebBreadcrumbs(
@ -507,7 +512,10 @@ class _WebNodesPageState extends State<WebNodesPage> {
},
),
),
Container(width: 1, color: context.palette.strokeSoft),
Container(
width: 1,
color: context.palette.strokeSoft,
),
Expanded(
child: _ResourceDetailPanel(
title: _tabLabel(_tab),
@ -529,81 +537,86 @@ class _WebNodesPageState extends State<WebNodesPage> {
List<_WorkspaceResourceItem> _itemsForTab(AppController controller) {
return switch (_tab) {
_WebNodesTab.nodes => controller.instances
.map(
(item) => _WorkspaceResourceItem(
id: item.id,
title: item.host?.trim().isNotEmpty == true ? item.host! : item.id,
subtitle: [
item.platform,
item.deviceFamily,
item.ip,
].whereType<String>().where((item) => item.trim().isNotEmpty).join(' · '),
status: item.mode ?? item.reason ?? appText('未知', 'Unknown'),
detailLines: <String>[
'${appText('实例 ID', 'Instance ID')}: ${item.id}',
if (item.version?.trim().isNotEmpty == true)
'${appText('版本', 'Version')}: ${item.version}',
if (item.modelIdentifier?.trim().isNotEmpty == true)
'${appText('机型', 'Model')}: ${item.modelIdentifier}',
if (item.text.trim().isNotEmpty)
'${appText('状态说明', 'Status note')}: ${item.text}',
],
),
)
.toList(growable: false),
_WebNodesTab.agents => controller.agents
.map(
(item) => _WorkspaceResourceItem(
id: item.id,
title: '${item.emoji} ${item.name}',
subtitle: item.id,
status: item.theme,
detailLines: <String>[
'${appText('代理 ID', 'Agent ID')}: ${item.id}',
'${appText('主题', 'Theme')}: ${item.theme}',
],
),
)
.toList(growable: false),
_WebNodesTab.connectors => controller.connectors
.map(
(item) => _WorkspaceResourceItem(
id: '${item.id}:${item.accountName ?? 'default'}',
title: item.label,
subtitle: [
item.detailLabel,
item.accountName,
].whereType<String>().where((item) => item.trim().isNotEmpty).join(' · '),
status: item.status,
detailLines: <String>[
'${appText('连接器', 'Connector')}: ${item.id}',
'${appText('状态', 'Status')}: ${item.status}',
if (item.meta.isNotEmpty) item.meta.join(' · '),
if (item.lastError?.trim().isNotEmpty == true)
'${appText('错误', 'Error')}: ${item.lastError}',
],
),
)
.toList(growable: false),
_WebNodesTab.models => controller.models
.map(
(item) => _WorkspaceResourceItem(
id: item.id,
title: item.name,
subtitle: item.provider,
status: item.id,
detailLines: <String>[
'${appText('模型 ID', 'Model ID')}: ${item.id}',
'${appText('提供方', 'Provider')}: ${item.provider}',
if (item.contextWindow != null)
'${appText('上下文窗口', 'Context window')}: ${item.contextWindow}',
if (item.maxOutputTokens != null)
'${appText('最大输出', 'Max output')}: ${item.maxOutputTokens}',
],
),
)
.toList(growable: false),
_WebNodesTab.nodes =>
controller.instances
.map(
(item) => _WorkspaceResourceItem(
id: item.id,
title: item.host?.trim().isNotEmpty == true
? item.host!
: item.id,
subtitle: [item.platform, item.deviceFamily, item.ip]
.whereType<String>()
.where((item) => item.trim().isNotEmpty)
.join(' · '),
status: item.mode ?? item.reason ?? appText('未知', 'Unknown'),
detailLines: <String>[
'${appText('实例 ID', 'Instance ID')}: ${item.id}',
if (item.version?.trim().isNotEmpty == true)
'${appText('版本', 'Version')}: ${item.version}',
if (item.modelIdentifier?.trim().isNotEmpty == true)
'${appText('机型', 'Model')}: ${item.modelIdentifier}',
if (item.text.trim().isNotEmpty)
'${appText('状态说明', 'Status note')}: ${item.text}',
],
),
)
.toList(growable: false),
_WebNodesTab.agents =>
controller.agents
.map(
(item) => _WorkspaceResourceItem(
id: item.id,
title: '${item.emoji} ${item.name}',
subtitle: item.id,
status: item.theme,
detailLines: <String>[
'${appText('代理 ID', 'Agent ID')}: ${item.id}',
'${appText('主题', 'Theme')}: ${item.theme}',
],
),
)
.toList(growable: false),
_WebNodesTab.connectors =>
controller.connectors
.map(
(item) => _WorkspaceResourceItem(
id: '${item.id}:${item.accountName ?? 'default'}',
title: item.label,
subtitle: [item.detailLabel, item.accountName]
.whereType<String>()
.where((item) => item.trim().isNotEmpty)
.join(' · '),
status: item.status,
detailLines: <String>[
'${appText('连接器', 'Connector')}: ${item.id}',
'${appText('状态', 'Status')}: ${item.status}',
if (item.meta.isNotEmpty) item.meta.join(' · '),
if (item.lastError?.trim().isNotEmpty == true)
'${appText('错误', 'Error')}: ${item.lastError}',
],
),
)
.toList(growable: false),
_WebNodesTab.models =>
controller.models
.map(
(item) => _WorkspaceResourceItem(
id: item.id,
title: item.name,
subtitle: item.provider,
status: item.id,
detailLines: <String>[
'${appText('模型 ID', 'Model ID')}: ${item.id}',
'${appText('提供方', 'Provider')}: ${item.provider}',
if (item.contextWindow != null)
'${appText('上下文窗口', 'Context window')}: ${item.contextWindow}',
if (item.maxOutputTokens != null)
'${appText('最大输出', 'Max output')}: ${item.maxOutputTokens}',
],
),
)
.toList(growable: false),
};
}
@ -724,7 +737,8 @@ class _WebSecretsPageState extends State<WebSecretsPage> {
),
),
FilledButton.tonalIcon(
onPressed: () => controller.openSettings(tab: SettingsTab.gateway),
onPressed: () =>
controller.openSettings(tab: SettingsTab.gateway),
icon: const Icon(Icons.tune_rounded),
label: Text(appText('打开设置', 'Open settings')),
),
@ -737,7 +751,10 @@ class _WebSecretsPageState extends State<WebSecretsPage> {
SurfaceCard(
child: Row(
children: [
Icon(Icons.shield_outlined, color: context.palette.accent),
Icon(
Icons.shield_outlined,
color: context.palette.accent,
),
const SizedBox(width: 12),
Expanded(
child: Text(
@ -769,10 +786,11 @@ class _WebSecretsPageState extends State<WebSecretsPage> {
},
),
),
Container(width: 1, color: context.palette.strokeSoft),
Expanded(
child: _SecretDetailPanel(item: selected),
Container(
width: 1,
color: context.palette.strokeSoft,
),
Expanded(child: _SecretDetailPanel(item: selected)),
],
),
),
@ -883,9 +901,8 @@ class _WebAiGatewayPageState extends State<WebAiGatewayPage> {
),
),
FilledButton.tonalIcon(
onPressed: () => widget.controller.openSettings(
tab: SettingsTab.gateway,
),
onPressed: () =>
widget.controller.openSettings(tab: SettingsTab.gateway),
icon: const Icon(Icons.tune_rounded),
label: Text(appText('打开设置', 'Open settings')),
),
@ -908,12 +925,19 @@ class _WebAiGatewayPageState extends State<WebAiGatewayPage> {
),
const SizedBox(height: 6),
Text(
controller.settings.aiGateway.baseUrl.trim().isEmpty
? appText('当前还没有配置 endpoint。', 'No endpoint is configured yet.')
: controller.settings.aiGateway.baseUrl.trim(),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: context.palette.textSecondary,
),
controller.settings.aiGateway.baseUrl
.trim()
.isEmpty
? appText(
'当前还没有配置 endpoint。',
'No endpoint is configured yet.',
)
: controller.settings.aiGateway.baseUrl
.trim(),
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: context.palette.textSecondary,
),
),
],
),
@ -949,7 +973,10 @@ class _WebAiGatewayPageState extends State<WebAiGatewayPage> {
},
),
),
Container(width: 1, color: context.palette.strokeSoft),
Container(
width: 1,
color: context.palette.strokeSoft,
),
Expanded(child: _ModelDetailPanel(model: selected)),
],
),
@ -1379,56 +1406,14 @@ class _SkillListTile extends StatelessWidget {
]
: const [],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
skill.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 10),
StatusBadge(
status: skill.disabled
? _skillStatus(
appText('已禁用', 'Disabled'),
StatusTone.warning,
)
: _skillStatus(
appText('已启用', 'Enabled'),
StatusTone.success,
),
),
],
),
const SizedBox(height: 8),
Text(
skill.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: palette.textSecondary,
height: 1.4,
),
),
const SizedBox(height: 10),
Wrap(
spacing: 10,
runSpacing: 6,
children: [
_SkillMeta(label: skill.source),
_SkillMeta(label: skill.primaryEnv ?? 'workspace'),
],
),
],
child: Text(
skill.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
color: selected ? palette.textPrimary : null,
),
),
),
),
@ -1672,22 +1657,6 @@ class _InlineMeta extends StatelessWidget {
}
}
class _SkillMeta extends StatelessWidget {
const _SkillMeta({required this.label});
final String label;
@override
Widget build(BuildContext context) {
return Text(
label,
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: context.palette.textMuted),
);
}
}
StatusInfo _taskStatusInfo(String status) => switch (status) {
'running' ||
'Running' => StatusInfo(appText('运行中', 'Running'), StatusTone.accent),
@ -1712,14 +1681,16 @@ class _WorkspaceStatusBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
final connected = controller.connection.status ==
RuntimeConnectionStatus.connected;
final connected =
controller.connection.status == RuntimeConnectionStatus.connected;
return SurfaceCard(
child: Row(
children: [
Icon(
connected ? Icons.check_circle_outline_rounded : Icons.info_outline,
color: connected ? context.palette.success : context.palette.warning,
color: connected
? context.palette.success
: context.palette.warning,
),
const SizedBox(width: 12),
Expanded(
@ -1897,18 +1868,13 @@ class _ResourceDetailPanel extends StatelessWidget {
Wrap(
spacing: 8,
runSpacing: 8,
children: [
Chip(label: Text(item!.status)),
],
children: [Chip(label: Text(item!.status))],
),
const SizedBox(height: 16),
...item!.detailLines.map(
(line) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
line,
style: Theme.of(context).textTheme.bodyMedium,
),
child: Text(line, style: Theme.of(context).textTheme.bodyMedium),
),
),
],
@ -1977,13 +1943,15 @@ class _SecretListPanel extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.name, style: Theme.of(context).textTheme.titleSmall),
Text(
item.name,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 4),
Text(
'${item.provider} · ${item.module}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: palette.textSecondary,
),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(color: palette.textSecondary),
),
const SizedBox(height: 8),
Text(item.maskedValue),
@ -2081,10 +2049,7 @@ class _ModelListPanel extends StatelessWidget {
child: items.isEmpty
? Center(
child: Text(
appText(
'当前没有可显示的模型。',
'No models are available yet.',
),
appText('当前没有可显示的模型。', 'No models are available yet.'),
),
)
: ListView.separated(
@ -2102,13 +2067,15 @@ class _ModelListPanel extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.name, style: Theme.of(context).textTheme.titleSmall),
Text(
item.name,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 4),
Text(
item.provider,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: palette.textSecondary,
),
style: Theme.of(context).textTheme.bodySmall
?.copyWith(color: palette.textSecondary),
),
const SizedBox(height: 8),
Text(item.id),