187 lines
5.6 KiB
Dart
187 lines
5.6 KiB
Dart
import "package:provider/provider.dart";
|
|
import "package:shadcn_flutter/shadcn_flutter.dart";
|
|
import '../../../src/session/session_types.dart';
|
|
import '../../providers/chat_provider.dart';
|
|
import '../../providers/home_coordinator.dart';
|
|
import '../../providers/projects_provider.dart';
|
|
import '../../providers/session_provider.dart';
|
|
import 'app_logo.dart';
|
|
import 'project_section.dart';
|
|
import 'thread_button.dart';
|
|
|
|
class Sidebar extends StatelessWidget {
|
|
|
|
const Sidebar({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final projectsProvider = context.watch<ProjectsProvider>();
|
|
final sessionProvider = context.watch<SessionProvider>();
|
|
final chatProvider = context.watch<ChatProvider>();
|
|
final coordinator = context.read<HomeCoordinator>();
|
|
|
|
return Container(
|
|
width: 300,
|
|
color: Theme.of(context).colorScheme.input.scaleAlpha(0.3),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
|
|
Container(
|
|
height: 100,
|
|
alignment: Alignment.centerLeft,
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
child: AppLogo(),
|
|
),
|
|
|
|
Divider(),
|
|
|
|
Gap(16),
|
|
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
|
child: _fixedSection(context, coordinator, chatProvider),
|
|
),
|
|
|
|
Gap(16),
|
|
|
|
Divider(),
|
|
|
|
Gap(16),
|
|
|
|
Expanded(
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 8),
|
|
child: _projectsSection(context, projectsProvider, sessionProvider, coordinator, chatProvider),
|
|
),
|
|
),
|
|
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _fixedSection(BuildContext context, HomeCoordinator coordinator, ChatProvider chatProvider) {
|
|
return Column(
|
|
children: [
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: Button.ghost(
|
|
style: ButtonStyle.ghost().copyWith(
|
|
padding: (context, state, edgeInsets) {
|
|
return EdgeInsets.only(
|
|
top: 8,
|
|
left: 8,
|
|
bottom: 8,
|
|
right: 10
|
|
);
|
|
}
|
|
),
|
|
onPressed: chatProvider.isLoading ? null : coordinator.createNewChat,
|
|
disableFocusOutline: true,
|
|
leading: Icon(LucideIcons.squarePen).iconSmall,
|
|
alignment: Alignment.centerLeft,
|
|
child: Text("New Chat"),
|
|
),
|
|
),
|
|
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: Button.ghost(
|
|
style: ButtonStyle.ghost().copyWith(
|
|
padding: (context, state, edgeInsets) {
|
|
return EdgeInsets.only(
|
|
top: 8,
|
|
left: 8,
|
|
bottom: 8,
|
|
right: 10
|
|
);
|
|
}
|
|
),
|
|
onPressed: coordinator.pickProjectDirectory,
|
|
disableFocusOutline: true,
|
|
leading: Icon(LucideIcons.folderPlus).iconSmall,
|
|
alignment: Alignment.centerLeft,
|
|
child: Text("New Project"),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _projectsSection(BuildContext context, ProjectsProvider projectsProvider, SessionProvider sessionProvider, HomeCoordinator coordinator, ChatProvider chatProvider) {
|
|
if (projectsProvider.projects.isEmpty) {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
|
child: Text("No projects yet").textSmall.muted,
|
|
);
|
|
}
|
|
|
|
// group sessions by working directory
|
|
final sessionsByProject = <String, List<SessionSummary>>{};
|
|
for (final session in sessionProvider.sessions) {
|
|
final dir = session.workingDirectory ?? '';
|
|
sessionsByProject.putIfAbsent(dir, () => []).add(session);
|
|
}
|
|
|
|
// sort sessions within each project newest first
|
|
final sorted = <String, List<SessionSummary>>{};
|
|
sessionsByProject.forEach((dir, sessions) {
|
|
sorted[dir] = List<SessionSummary>.from(sessions)
|
|
..sort((a, b) => b.updated.compareTo(a.updated));
|
|
});
|
|
|
|
final projects = List.of(projectsProvider.projects)
|
|
..sort((a, b) {
|
|
final aLatest = sorted[a.workingDirectory]?.firstOrNull?.updated;
|
|
final bLatest = sorted[b.workingDirectory]?.firstOrNull?.updated;
|
|
|
|
if (aLatest == null && bLatest == null) return 0;
|
|
if (aLatest == null) return 1;
|
|
if (bLatest == null) return -1;
|
|
return bLatest.compareTo(aLatest);
|
|
});
|
|
|
|
return ListView(
|
|
padding: EdgeInsets.zero,
|
|
children: [
|
|
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
child: Text("Projects").textMuted,
|
|
),
|
|
|
|
Gap(8),
|
|
|
|
for (final project in projects) ...[
|
|
|
|
ProjectSection(
|
|
projectLabel: project.name,
|
|
children: [
|
|
if (sorted[project.workingDirectory]?.isEmpty ?? true)
|
|
ThreadButton(
|
|
label: "No threads yet",
|
|
muted: true,
|
|
)
|
|
else
|
|
for (final session in sorted[project.workingDirectory]!)
|
|
ThreadButton(
|
|
label: session.name,
|
|
lastMessage: session.updated,
|
|
selected: sessionProvider.currentSessionId == session.id,
|
|
isRunning: chatProvider.isSessionRunning(session.id),
|
|
onPressed: () => coordinator.openSession(session),
|
|
onDelete: () => coordinator.deleteSession(session),
|
|
),
|
|
],
|
|
),
|
|
|
|
Gap(2),
|
|
|
|
],
|
|
|
|
],
|
|
);
|
|
}
|
|
|
|
}
|