Add command files and enhance session management features

This commit is contained in:
ImBenji
2026-04-28 19:00:27 +01:00
parent 3588783001
commit 728c0ffe81
146 changed files with 6854 additions and 7783 deletions
@@ -1,4 +1,5 @@
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../permission_decision.dart";
import "tool_bubble_base.dart";
class AdvisorBubble extends StatelessWidget {
@@ -6,12 +7,12 @@ class AdvisorBubble extends StatelessWidget {
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -21,7 +22,7 @@ class AdvisorBubble extends StatelessWidget {
toolName: "Advisor",
icon: LucideIcons.brain,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
detail: model,
);
}
@@ -1,28 +1,397 @@
import "dart:io";
import "package:shadcn_flutter/shadcn_flutter.dart";
import "tool_bubble_base.dart";
import "package:provider/provider.dart";
import "../../../../providers/chat_provider.dart";
import "../permission_decision.dart";
import "../../../common/pane_dialog.dart";
class BashBubble extends StatelessWidget {
class BashBubble extends StatefulWidget {
const BashBubble({
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
State<BashBubble> createState() => _BashBubbleState();
}
class _BashBubbleState extends State<BashBubble> {
final _scrollController = ScrollController();
late final TextEditingController _ruleController;
bool _overflows = false;
@override
void initState() {
super.initState();
_ruleController = TextEditingController(
text: widget.pendingPermission?.suggestionRule ?? "Bash",
);
WidgetsBinding.instance.addPostFrameCallback((_) => _checkOverflow());
}
@override
void didUpdateWidget(BashBubble oldWidget) {
super.didUpdateWidget(oldWidget);
final newRule = widget.pendingPermission?.suggestionRule ?? "Bash";
final oldRule = oldWidget.pendingPermission?.suggestionRule ?? "Bash";
if (newRule != oldRule) _ruleController.text = newRule;
if (oldWidget.result != widget.result) {
WidgetsBinding.instance.addPostFrameCallback((_) => _checkOverflow());
}
}
@override
void dispose() {
_scrollController.dispose();
_ruleController.dispose();
super.dispose();
}
void _openFullscreen(
BuildContext context,
String command,
Color termBg,
Color termTitleBg,
Color termBorder,
Color outputGray,
) {
final theme = Theme.of(context);
showDialog(
context: context,
builder: (ctx) => Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: SizedBox(
width: double.infinity,
height: double.infinity,
child: PaneDialog(
title: "Terminal",
fillHeight: true,
onClose: () => Navigator.of(ctx).pop(),
child: Flexible(
child: Container(
color: termBg,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(14, 12, 14, 6),
child: _TerminalPrompt(
command: command,
cwd: widget.input["cwd"] as String? ??
context.read<ChatProvider>().workingDirectory,
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(14, 0, 14, 16),
child: SelectableText(
widget.result ?? "",
style: theme.typography.mono.copyWith(
color: outputGray,
fontSize: 12,
height: 1.4,
),
),
),
),
],
),
),
),
),
),
),
),
);
}
void _checkOverflow() {
if (!_scrollController.hasClients) return;
final overflows = _scrollController.position.maxScrollExtent > 0;
if (overflows != _overflows) setState(() => _overflows = overflows);
}
@override
Widget build(BuildContext context) {
final command = input["command"] as String? ?? "";
final command = widget.input["command"] as String? ?? "";
final hasResult = widget.result != null && widget.result!.isNotEmpty;
return ToolBubbleBase(
toolName: "Bash",
icon: LucideIcons.terminal,
result: result,
isPendingPermission: isPendingPermission,
detail: command,
final theme = Theme.of(context);
final dark = theme.brightness == Brightness.dark;
final h = HSLColor.fromColor(theme.colorScheme.border).hue;
final s = HSLColor.fromColor(theme.colorScheme.border).saturation;
final termBg = dark
? HSLColor.fromAHSL(1, h, s.clamp(0, 0.35), 0.10).toColor()
: HSLColor.fromAHSL(1, h, s.clamp(0, 0.30), 0.92).toColor();
final termTitleBg = dark
? HSLColor.fromAHSL(1, h, s.clamp(0, 0.35), 0.14).toColor()
: HSLColor.fromAHSL(1, h, s.clamp(0, 0.30), 0.87).toColor();
final termBorder = theme.colorScheme.border;
final outputGray = theme.colorScheme.mutedForeground;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: termBg,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: termBorder, width: 1),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// title bar
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: termTitleBg,
border: Border(
bottom: BorderSide(color: termBorder, width: 1),
),
),
child: Row(
children: [
Text(
"Terminal",
style: theme.typography.p.copyWith(
color: theme.colorScheme.mutedForeground,
fontFamily: "monospace",
fontSize: 11,
),
),
const Spacer(),
if (hasResult)
GestureDetector(
onTap: () => _openFullscreen(context, command, termBg, termTitleBg, termBorder, outputGray),
child: Icon(
LucideIcons.maximize2,
size: 13,
color: theme.colorScheme.mutedForeground,
),
),
],
),
),
// prompt always visible
Padding(
padding: const EdgeInsets.fromLTRB(14, 10, 14, 6),
child: _TerminalPrompt(
command: command,
cwd: widget.input["cwd"] as String? ??
context.read<ChatProvider>().workingDirectory,
),
),
// output — shrinks to content, caps at maxHeight, gradient only when overflowing
if (hasResult)
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 320),
child: ShaderMask(
shaderCallback: (rect) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
_overflows ? Colors.transparent : Colors.white,
Colors.white,
],
stops: const [0.0, 0.15],
).createShader(rect),
blendMode: BlendMode.dstIn,
child: SingleChildScrollView(
controller: _scrollController,
reverse: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(14, 0, 14, 12),
child: SelectableText(
widget.result!,
style: theme.typography.mono.copyWith(
color: outputGray,
fontSize: 11,
height: 1.25,
),
),
),
),
),
if (!hasResult) const SizedBox(height: 10),
],
),
),
),
if (widget.pendingPermission != null) ...[
const SizedBox(height: 8),
Row(
children: [
Text("Don't ask again for:").xSmall,
const Gap(8),
Expanded(
child: TextField(
controller: _ruleController,
style: const TextStyle(fontSize: 12, fontFamily: "monospace"),
),
),
],
),
const SizedBox(height: 6),
Row(
children: [
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.check).iconSmall,
onPressed: () => context.read<ChatProvider>().resolvePermission(PermissionDecision.allowOnce),
child: Text("Yes").small,
),
),
const SizedBox(width: 8),
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.checkCheck).iconSmall,
onPressed: () => context.read<ChatProvider>().resolvePermission(
PermissionDecision.allowAlways,
persistRule: _ruleController.text.trim(),
),
child: Text("Yes, don't ask again").small,
),
),
const SizedBox(width: 8),
Expanded(
child: Button.destructive(
leading: Icon(LucideIcons.x).iconSmall,
onPressed: () => context.read<ChatProvider>().resolvePermission(PermissionDecision.reject),
child: Text("No").small,
),
),
],
),
],
],
);
}
}
class _TerminalPrompt extends StatelessWidget {
const _TerminalPrompt({required this.command, this.cwd});
final String command;
final String? cwd;
String _shortCwd() {
try {
final home = Platform.environment["HOME"] ?? "";
var resolved = cwd ?? Directory.current.path;
if (home.isNotEmpty && resolved.startsWith(home)) {
resolved = "~${resolved.substring(home.length)}";
}
return resolved;
} catch (_) {
return "~";
}
}
String _hostname() {
try {
return Platform.localHostname.split(".").first;
} catch (_) {
return "localhost";
}
}
@override
Widget build(BuildContext context) {
final hostname = _hostname();
final cwd = _shortCwd();
final theme = Theme.of(context);
final monoBase = theme.typography.mono.copyWith(
fontSize: 11,
height: 1.25,
);
final fg = theme.colorScheme.foreground;
return RichText(
text: TextSpan(
style: monoBase,
children: [
// the_agency@hostname — green
TextSpan(
text: "the_agency@$hostname",
style: const TextStyle(color: Color(0xFF4EC94E), fontWeight: FontWeight.w600),
),
TextSpan(text: ":", style: TextStyle(color: fg)),
// cwd — blue
TextSpan(
text: cwd,
style: const TextStyle(color: Color(0xFF5BB8FF)),
),
TextSpan(text: "\$ ", style: TextStyle(color: fg)),
TextSpan(text: command, style: TextStyle(color: fg)),
],
),
);
}
}
class _Dot extends StatelessWidget {
const _Dot({required this.color});
final Color color;
@override
Widget build(BuildContext context) {
return Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
);
}
}
@@ -1,5 +1,6 @@
import "dart:convert";
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../permission_decision.dart";
import "tool_bubble_base.dart";
class DefaultToolBubble extends StatelessWidget {
@@ -8,13 +9,13 @@ class DefaultToolBubble extends StatelessWidget {
required this.toolName,
this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final String toolName;
final Map<String, dynamic>? input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -24,7 +25,7 @@ class DefaultToolBubble extends StatelessWidget {
toolName: toolName,
icon: LucideIcons.wrench,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
body: input != null && input!.isNotEmpty
? Padding(
padding: const EdgeInsets.only(left: 4),
@@ -1,6 +1,7 @@
import "package:provider/provider.dart";
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../../../../providers/chat_provider.dart";
import "../permission_decision.dart";
import "../../../../utils/path_utils.dart";
import "../../diff_view.dart";
import "tool_bubble_base.dart";
@@ -10,12 +11,12 @@ class EditBubble extends StatelessWidget {
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -28,7 +29,7 @@ class EditBubble extends StatelessWidget {
toolName: "Edit",
icon: LucideIcons.filePen,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
detail: shortenPath(filePath, projectRoot),
body: DiffView(
oldString: oldString,
@@ -1,6 +1,7 @@
import "package:provider/provider.dart";
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../../../../providers/chat_provider.dart";
import "../permission_decision.dart";
import "../../../../utils/path_utils.dart";
import "tool_bubble_base.dart";
@@ -9,12 +10,12 @@ class GlobBubble extends StatelessWidget {
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -30,7 +31,7 @@ class GlobBubble extends StatelessWidget {
toolName: "Glob",
icon: LucideIcons.folderSearch,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
detail: detail,
);
}
@@ -1,6 +1,7 @@
import "package:provider/provider.dart";
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../../../../providers/chat_provider.dart";
import "../permission_decision.dart";
import "../../../../utils/path_utils.dart";
import "tool_bubble_base.dart";
@@ -9,12 +10,12 @@ class GrepBubble extends StatelessWidget {
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -30,7 +31,7 @@ class GrepBubble extends StatelessWidget {
toolName: "Grep",
icon: LucideIcons.search,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
detail: detail,
);
}
@@ -1,6 +1,7 @@
import "package:provider/provider.dart";
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../../../../providers/chat_provider.dart";
import "../permission_decision.dart";
import "../../../../utils/path_utils.dart";
import "tool_bubble_base.dart";
@@ -9,12 +10,12 @@ class ReadBubble extends StatelessWidget {
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -25,7 +26,7 @@ class ReadBubble extends StatelessWidget {
toolName: "Read",
icon: LucideIcons.fileText,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
detail: shortenPath(filePath, projectRoot),
);
}
@@ -3,7 +3,7 @@ import "package:shadcn_flutter/shadcn_flutter.dart";
import "../../../../providers/chat_provider.dart";
import "../permission_decision.dart";
class ToolBubbleBase extends StatelessWidget {
class ToolBubbleBase extends StatefulWidget {
const ToolBubbleBase({
super.key,
required this.toolName,
@@ -11,7 +11,7 @@ class ToolBubbleBase extends StatelessWidget {
this.detail,
this.body,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final String toolName;
@@ -19,17 +19,77 @@ class ToolBubbleBase extends StatelessWidget {
final String? detail;
final Widget? body;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
State<ToolBubbleBase> createState() => _ToolBubbleBaseState();
}
class _ToolBubbleBaseState extends State<ToolBubbleBase> {
late final TextEditingController _ruleController;
@override
void initState() {
super.initState();
_ruleController = TextEditingController(
text: widget.pendingPermission?.suggestionRule ?? widget.toolName,
);
}
@override
void didUpdateWidget(ToolBubbleBase old) {
super.didUpdateWidget(old);
final newRule = widget.pendingPermission?.suggestionRule ?? widget.toolName;
final oldRule = old.pendingPermission?.suggestionRule ?? old.toolName;
if (newRule != oldRule) {
_ruleController.text = newRule;
}
}
@override
void dispose() {
_ruleController.dispose();
super.dispose();
}
bool get _isFileTool {
const ft = {"Edit", "Write", "Read", "MultiEdit"};
return ft.contains(widget.toolName);
}
bool get _isBash => widget.toolName == "Bash";
bool get _isWebFetch => widget.toolName == "WebFetch" || widget.toolName == "WebSearch";
// extract hostname from a url for the WebFetch "dont ask again" label
String _hostnameFromPending() {
final pp = widget.pendingPermission;
if (pp == null) return "";
final url = pp.input["url"] as String? ?? pp.input["query"] as String? ?? "";
try {
return Uri.parse(url).host;
} catch (_) {
return url;
}
}
// rule to persist for webfetch — "WebFetch(hostname:*)"
String _webFetchPersistRule() {
final host = _hostnameFromPending();
if (host.isEmpty) return widget.toolName;
return "WebFetch($host:*)";
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final pp = widget.pendingPermission;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
OutlinedContainer(
clipBehavior: Clip.antiAlias,
child: Column(
@@ -47,24 +107,17 @@ class ToolBubbleBase extends StatelessWidget {
),
child: Row(
children: [
Icon(icon).iconSmall,
Icon(widget.icon).iconSmall,
Gap(8),
Text(
toolName,
).textSmall,
Text(widget.toolName).textSmall,
],
),
),
VerticalDivider(),
if (detail != null)...[
if (widget.detail != null)...[
Gap(16),
Text(
detail!,
).mono.xSmall
Text(widget.detail!).mono.xSmall
]
],
@@ -72,14 +125,12 @@ class ToolBubbleBase extends StatelessWidget {
),
if (body != null) ...[
if (widget.body != null) ...[
Divider(),
body!,
widget.body!,
],
if (result != null) ...[
if (widget.result != null) ...[
Divider(),
Padding(
@@ -88,7 +139,7 @@ class ToolBubbleBase extends StatelessWidget {
vertical: 8,
),
child: SelectableText(
"\u200B${result!}",
"\u200B${widget.result!}",
style: TextStyle(
color: theme.colorScheme.mutedForeground,
),
@@ -96,50 +147,253 @@ class ToolBubbleBase extends StatelessWidget {
)
]
],
),
),
if (isPendingPermission) ...[
if (pp != null) ...[
Gap(8),
Row(
children: [
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.check).iconSmall,
onPressed: () => context.read<ChatProvider>().resolvePermission(PermissionDecision.allowOnce),
child: Text("Yes").small,
),
),
if (_isBash) ...[
// bash: editable rule field + buttons
_BashPermissionButtons(ruleController: _ruleController),
] else if (_isWebFetch) ...[
// webfetch: domain-scoped dont-ask-again
_WebFetchPermissionButtons(
hostname: _hostnameFromPending(),
persistRule: _webFetchPersistRule(),
),
] else if (_isFileTool) ...[
// file tools: session-scoped only, no persist
_FilePermissionButtons(),
] else ...[
// default: show rule in button
_DefaultPermissionButtons(
persistRule: pp.suggestionRule ?? widget.toolName,
),
],
Gap(8),
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.checkCheck).iconSmall,
onPressed: () => context.read<ChatProvider>().resolvePermission(PermissionDecision.allowAlways),
child: Text("Yes, for this session").small,
),
),
Gap(8),
Expanded(
child: Button.destructive(
leading: Icon(LucideIcons.x).iconSmall,
onPressed: () => context.read<ChatProvider>().resolvePermission(PermissionDecision.reject),
child: Text("No").small,
),
),
],
),
],
],
);
}
}
// ─── permission button sets ──────────────────────────────────────────────────
class _BashPermissionButtons extends StatelessWidget {
const _BashPermissionButtons({required this.ruleController});
final TextEditingController ruleController;
@override
Widget build(BuildContext context) {
final chat = context.read<ChatProvider>();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Text("Don't ask again for:").xSmall,
Gap(8),
Expanded(
child: TextField(
controller: ruleController,
style: const TextStyle(fontSize: 12, fontFamily: "monospace"),
),
),
],
),
Gap(6),
Row(
children: [
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.check).iconSmall,
onPressed: () => chat.resolvePermission(PermissionDecision.allowOnce),
child: Text("Yes").small,
),
),
Gap(8),
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.checkCheck).iconSmall,
onPressed: () => chat.resolvePermission(
PermissionDecision.allowAlways,
persistRule: ruleController.text.trim(),
),
child: Text("Yes, don't ask again").small,
),
),
Gap(8),
Expanded(
child: Button.destructive(
leading: Icon(LucideIcons.x).iconSmall,
onPressed: () => chat.resolvePermission(PermissionDecision.reject),
child: Text("No").small,
),
),
],
),
],
);
}
}
class _WebFetchPermissionButtons extends StatelessWidget {
const _WebFetchPermissionButtons({required this.hostname, required this.persistRule});
final String hostname;
final String persistRule;
@override
Widget build(BuildContext context) {
final chat = context.read<ChatProvider>();
return Row(
children: [
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.check).iconSmall,
onPressed: () => chat.resolvePermission(PermissionDecision.allowOnce),
child: Text("Yes").small,
),
),
Gap(8),
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.checkCheck).iconSmall,
onPressed: () => chat.resolvePermission(
PermissionDecision.allowAlways,
persistRule: persistRule,
),
child: hostname.isNotEmpty
? Text("Yes, always for $hostname").small
: Text("Yes, don't ask again").small,
),
),
Gap(8),
Expanded(
child: Button.destructive(
leading: Icon(LucideIcons.x).iconSmall,
onPressed: () => chat.resolvePermission(PermissionDecision.reject),
child: Text("No").small,
),
),
],
);
}
}
class _FilePermissionButtons extends StatelessWidget {
const _FilePermissionButtons();
@override
Widget build(BuildContext context) {
final chat = context.read<ChatProvider>();
return Row(
children: [
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.check).iconSmall,
onPressed: () => chat.resolvePermission(PermissionDecision.allowOnce),
child: Text("Yes").small,
),
),
Gap(8),
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.checkCheck).iconSmall,
// no persistRule — session-scoped only
onPressed: () => chat.resolvePermission(PermissionDecision.allowAlways),
child: Text("Yes, for this session").small,
),
),
Gap(8),
Expanded(
child: Button.destructive(
leading: Icon(LucideIcons.x).iconSmall,
onPressed: () => chat.resolvePermission(PermissionDecision.reject),
child: Text("No").small,
),
),
],
);
}
}
class _DefaultPermissionButtons extends StatelessWidget {
const _DefaultPermissionButtons({required this.persistRule});
final String persistRule;
@override
Widget build(BuildContext context) {
final chat = context.read<ChatProvider>();
return Row(
children: [
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.check).iconSmall,
onPressed: () => chat.resolvePermission(PermissionDecision.allowOnce),
child: Text("Yes").small,
),
),
Gap(8),
Expanded(
child: Button.outline(
leading: Icon(LucideIcons.checkCheck).iconSmall,
onPressed: () => chat.resolvePermission(
PermissionDecision.allowAlways,
persistRule: persistRule,
),
child: Text("Yes, don't ask again").small,
),
),
Gap(8),
Expanded(
child: Button.destructive(
leading: Icon(LucideIcons.x).iconSmall,
onPressed: () => chat.resolvePermission(PermissionDecision.reject),
child: Text("No").small,
),
),
],
);
}
}
@@ -1,4 +1,5 @@
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../permission_decision.dart";
import "tool_bubble_base.dart";
class WebFetchBubble extends StatelessWidget {
@@ -6,12 +7,12 @@ class WebFetchBubble extends StatelessWidget {
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -21,7 +22,7 @@ class WebFetchBubble extends StatelessWidget {
toolName: "WebFetch",
icon: LucideIcons.link,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
detail: url,
);
}
@@ -1,4 +1,5 @@
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../permission_decision.dart";
import "tool_bubble_base.dart";
class WebSearchBubble extends StatelessWidget {
@@ -6,12 +7,12 @@ class WebSearchBubble extends StatelessWidget {
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -21,7 +22,7 @@ class WebSearchBubble extends StatelessWidget {
toolName: "WebSearch",
icon: LucideIcons.globe,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
detail: query,
);
}
@@ -1,6 +1,7 @@
import "package:provider/provider.dart";
import "package:shadcn_flutter/shadcn_flutter.dart";
import "../../../../providers/chat_provider.dart";
import "../permission_decision.dart";
import "../../../../utils/path_utils.dart";
import "../../diff_view.dart";
import "tool_bubble_base.dart";
@@ -10,12 +11,12 @@ class WriteBubble extends StatelessWidget {
super.key,
required this.input,
this.result,
this.isPendingPermission = false,
this.pendingPermission,
});
final Map<String, dynamic> input;
final String? result;
final bool isPendingPermission;
final PendingPermission? pendingPermission;
@override
Widget build(BuildContext context) {
@@ -27,7 +28,7 @@ class WriteBubble extends StatelessWidget {
toolName: "Write",
icon: LucideIcons.filePlus,
result: result,
isPendingPermission: isPendingPermission,
pendingPermission: pendingPermission,
detail: shortenPath(filePath, projectRoot),
body: DiffView(
oldString: "",