Add initial project files and configurations for clawd_code
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import "package:shadcn_flutter/shadcn_flutter.dart";
|
||||
|
||||
class GarageHeader extends StatelessWidget {
|
||||
const GarageHeader({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"THE AGENCY",
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"by IMBENJI.NET LTD",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.mutedForeground,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import "package:flutter/widgets.dart";
|
||||
import "package:provider/provider.dart";
|
||||
|
||||
import "../providers/chat_provider.dart";
|
||||
import "message_bubble.dart";
|
||||
|
||||
class ChatView extends StatefulWidget {
|
||||
const ChatView();
|
||||
|
||||
@override
|
||||
State<ChatView> createState() => _ChatViewState();
|
||||
}
|
||||
|
||||
class _ChatViewState extends State<ChatView> {
|
||||
late ScrollController _scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = ScrollController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scrollToBottom() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ChatProvider>(
|
||||
builder: (context, chatProvider, _) {
|
||||
// scroll to bottom when new messages arrive
|
||||
if (chatProvider.messages.isNotEmpty) {
|
||||
_scrollToBottom();
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
controller: _scrollController,
|
||||
itemCount: chatProvider.messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final message = chatProvider.messages[index];
|
||||
return MessageBubble(message: message);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import "package:provider/provider.dart";
|
||||
import "package:shadcn_flutter/shadcn_flutter.dart";
|
||||
|
||||
import "../providers/cost_provider.dart";
|
||||
|
||||
class CostBadge extends StatelessWidget {
|
||||
const CostBadge();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<CostProvider>(
|
||||
builder: (context, costProvider, _) {
|
||||
final costStr = costProvider.getFormattedTotalCost();
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F5F9),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
costStr,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF475569),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import "package:provider/provider.dart";
|
||||
import "package:shadcn_flutter/shadcn_flutter.dart";
|
||||
|
||||
import "../providers/chat_provider.dart";
|
||||
|
||||
class InputBar extends StatefulWidget {
|
||||
const InputBar({super.key});
|
||||
|
||||
@override
|
||||
State<InputBar> createState() => _InputBarState();
|
||||
}
|
||||
|
||||
class _InputBarState extends State<InputBar> {
|
||||
late TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _send(ChatProvider provider) {
|
||||
final text = _controller.text.trim();
|
||||
if (text.isEmpty) return;
|
||||
provider.sendMessage(text);
|
||||
_controller.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ChatProvider>(
|
||||
builder: (context, chatProvider, _) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
minLines: 1,
|
||||
maxLines: 4,
|
||||
placeholder: const Text("Type a message..."),
|
||||
enabled: !chatProvider.isLoading,
|
||||
onSubmitted: chatProvider.isLoading ? null : (_) => _send(chatProvider),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 10),
|
||||
|
||||
chatProvider.isLoading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(value: null),
|
||||
),
|
||||
)
|
||||
: PrimaryButton(
|
||||
onPressed: () => _send(chatProvider),
|
||||
child: const Text("Send"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import "package:flutter/material.dart" as material hide Card;
|
||||
import "package:flutter_markdown/flutter_markdown.dart";
|
||||
import "package:shadcn_flutter/shadcn_flutter.dart";
|
||||
|
||||
import "../../src/session/session_types.dart";
|
||||
|
||||
class MessageBubble extends StatelessWidget {
|
||||
const MessageBubble({required this.message});
|
||||
|
||||
final Message message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isUser = message.role == "user";
|
||||
final isTool = message.role == "tool";
|
||||
final isAssistant = message.role == "assistant";
|
||||
final accentColor = isTool
|
||||
? const Color(0xFF64748B)
|
||||
: const Color(0xFF94A3B8);
|
||||
|
||||
return Align(
|
||||
alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: material.MediaQuery.of(context).size.width * 0.7,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: material.Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
message.role,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: accentColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (isAssistant || isTool)
|
||||
MarkdownBody(
|
||||
data: isTool
|
||||
? _buildToolMarkdown(message.content)
|
||||
: message.content,
|
||||
selectable: true,
|
||||
shrinkWrap: true,
|
||||
styleSheet: isTool
|
||||
? _toolMarkdownStyleSheet(context)
|
||||
: null,
|
||||
)
|
||||
else
|
||||
Text(message.content),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _buildToolMarkdown(String content) {
|
||||
final lines = content.split("\n");
|
||||
if (lines.isEmpty) {
|
||||
return "```text\n\n```";
|
||||
}
|
||||
|
||||
final title = lines.first.trim();
|
||||
final body = lines.skip(1).join("\n").trimRight();
|
||||
if (body.isEmpty) {
|
||||
return title;
|
||||
}
|
||||
|
||||
return "$title\n\n```text\n$body\n```";
|
||||
}
|
||||
|
||||
MarkdownStyleSheet _toolMarkdownStyleSheet(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return MarkdownStyleSheet.fromTheme(material.Theme.of(context)).copyWith(
|
||||
p: theme.typography.base.copyWith(height: 1.35),
|
||||
codeblockDecoration: BoxDecoration(
|
||||
color: theme.colorScheme.muted.withValues(alpha: 0.35),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
codeblockPadding: const EdgeInsets.all(12),
|
||||
code: theme.typography.base.copyWith(
|
||||
fontFamily: "monospace",
|
||||
height: 1.35,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:provider/provider.dart";
|
||||
|
||||
import "../../src/api/openrouter_client.dart";
|
||||
import "../providers/settings_provider.dart";
|
||||
|
||||
class ModelPicker extends StatefulWidget {
|
||||
const ModelPicker();
|
||||
|
||||
@override
|
||||
State<ModelPicker> createState() => _ModelPickerState();
|
||||
}
|
||||
|
||||
class _ModelPickerState extends State<ModelPicker> {
|
||||
late Future<List<Map<String, dynamic>>> _modelsFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_modelsFuture = _loadModels();
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> _loadModels() async {
|
||||
try {
|
||||
final apiKey = context.read<SettingsProvider>().settings.openRouterApiKey;
|
||||
if (apiKey == null || apiKey.isEmpty) {
|
||||
return [
|
||||
{"id": "openai/gpt-4o", "name": "GPT-4o"},
|
||||
{"id": "anthropic/claude-sonnet-4.6", "name": "Claude Sonnet 4.6"},
|
||||
{"id": "google/gemini-2.0-flash-001", "name": "Gemini 2.0 Flash"},
|
||||
];
|
||||
}
|
||||
|
||||
final client = await OpenRouterClientFactory.create(apiKey: apiKey);
|
||||
final models = await client.listModels();
|
||||
client.close();
|
||||
return models;
|
||||
} catch (e) {
|
||||
return [
|
||||
{"id": "openai/gpt-4o", "name": "GPT-4o"},
|
||||
{"id": "anthropic/claude-sonnet-4.6", "name": "Claude Sonnet 4.6"},
|
||||
{"id": "google/gemini-2.0-flash-001", "name": "Gemini 2.0 Flash"},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, _) {
|
||||
final currentModel = settingsProvider.normalizeModelId(
|
||||
settingsProvider.settings.model,
|
||||
);
|
||||
|
||||
return FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _modelsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Text("Loading models...");
|
||||
}
|
||||
|
||||
final models = snapshot.data ?? [];
|
||||
final selectedModel = models.firstWhere(
|
||||
(m) => m["id"] == currentModel,
|
||||
orElse: () => {"id": currentModel, "name": currentModel},
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => _showModelMenu(
|
||||
context,
|
||||
models,
|
||||
currentModel,
|
||||
settingsProvider,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(selectedModel["name"] as String? ?? currentModel),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showModelMenu(
|
||||
BuildContext context,
|
||||
List<Map<String, dynamic>> models,
|
||||
String currentModel,
|
||||
SettingsProvider settingsProvider,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxHeight: 400),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: models.map((model) {
|
||||
final modelId = model["id"] as String;
|
||||
final modelName = model["name"] as String? ?? modelId;
|
||||
final isSelected = modelId == currentModel;
|
||||
|
||||
return Container(
|
||||
color: isSelected
|
||||
? const Color(0xFFF1F5F9)
|
||||
: Colors.transparent,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
settingsProvider.updateModel(modelId);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (isSelected)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Text(
|
||||
"✓",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Expanded(child: Text(modelName)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
import "package:provider/provider.dart";
|
||||
import "package:shadcn_flutter/shadcn_flutter.dart";
|
||||
|
||||
import "../providers/settings_provider.dart";
|
||||
import "model_picker.dart";
|
||||
|
||||
class SettingsSheet extends StatelessWidget {
|
||||
const SettingsSheet();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<SettingsProvider>(
|
||||
builder: (context, settingsProvider, _) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Settings",
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// model picker
|
||||
const Text(
|
||||
"Model",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const ModelPicker(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// API key setting
|
||||
const Text(
|
||||
"OpenRouter API Key",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_ApiKeyInput(settingsProvider: settingsProvider),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// theme setting
|
||||
const Text(
|
||||
"Theme",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_SimpleDropdown<String>(
|
||||
value: settingsProvider.settings.theme,
|
||||
items: const ["dark", "light"],
|
||||
onChanged: (newTheme) {
|
||||
settingsProvider.updateTheme(newTheme);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// effort level setting
|
||||
const Text(
|
||||
"Effort Level",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_SimpleDropdown<String>(
|
||||
value: settingsProvider.settings.effortLevel ?? "medium",
|
||||
items: const ["low", "medium", "high", "max"],
|
||||
onChanged: (newLevel) {
|
||||
settingsProvider.updateEffortLevel(newLevel);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// reset button
|
||||
Button.ghost(
|
||||
onPressed: () {
|
||||
settingsProvider.resetToDefaults();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text("Reset to Defaults"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ApiKeyInput extends StatefulWidget {
|
||||
final SettingsProvider settingsProvider;
|
||||
|
||||
const _ApiKeyInput({required this.settingsProvider});
|
||||
|
||||
@override
|
||||
State<_ApiKeyInput> createState() => _ApiKeyInputState();
|
||||
}
|
||||
|
||||
class _ApiKeyInputState extends State<_ApiKeyInput> {
|
||||
late TextEditingController _controller;
|
||||
bool _obscureText = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController(
|
||||
text: widget.settingsProvider.settings.openRouterApiKey ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
obscureText: _obscureText,
|
||||
onChanged: (value) {
|
||||
widget.settingsProvider.updateApiKey(value);
|
||||
},
|
||||
placeholder: const Text("sk-or-v1-..."),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_obscureText = !_obscureText;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
_obscureText ? "Show" : "Hide",
|
||||
style: const TextStyle(fontSize: 12, color: Color(0xFF999999)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SimpleDropdown<T> extends StatelessWidget {
|
||||
final T value;
|
||||
final List<T> items;
|
||||
final Function(T) onChanged;
|
||||
|
||||
const _SimpleDropdown({
|
||||
required this.value,
|
||||
required this.items,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => _showMenu(context),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(value.toString()),
|
||||
const Text("▼", style: TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showMenu(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: items
|
||||
.map((item) {
|
||||
final isSelected = item == value;
|
||||
return Container(
|
||||
color: isSelected ? const Color(0xFFF1F5F9) : Colors.transparent,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onChanged(item);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isSelected)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Text("✓", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
),
|
||||
Text(item.toString()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import "package:provider/provider.dart";
|
||||
import "package:shadcn_flutter/shadcn_flutter.dart";
|
||||
|
||||
import "../providers/chat_provider.dart";
|
||||
import "../providers/session_provider.dart";
|
||||
|
||||
class Sidebar extends StatelessWidget {
|
||||
const Sidebar();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<SessionProvider>(
|
||||
builder: (context, sessionProvider, _) {
|
||||
return Column(
|
||||
children: [
|
||||
// sessions list
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: sessionProvider.sessions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final session = sessionProvider.sessions[index];
|
||||
final isSelected =
|
||||
sessionProvider.currentSessionId == session.id;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await sessionProvider.loadSession(session.id);
|
||||
if (context.mounted) {
|
||||
final chatProvider =
|
||||
Provider.of<ChatProvider>(context, listen: false);
|
||||
chatProvider.setConversation(
|
||||
sessionProvider.getConversationHistory(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? const Color(0xFFEFF6FF)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
session.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: isSelected
|
||||
? FontWeight.w600
|
||||
: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${session.messageCount} msgs",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF94A3B8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// new session button
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: PrimaryButton(
|
||||
onPressed: () async {
|
||||
await sessionProvider.createNewSession();
|
||||
if (context.mounted) {
|
||||
final chatProvider =
|
||||
Provider.of<ChatProvider>(context, listen: false);
|
||||
chatProvider.setConversation(
|
||||
sessionProvider.getConversationHistory(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text("+ New"),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user