Skip to content

Commit d080e37

Browse files
authored
Merge pull request #68 from sjjian/dev_0_6_0
Dev 0 6 0
2 parents 52d5d07 + a0f7996 commit d080e37

File tree

6 files changed

+160
-65
lines changed

6 files changed

+160
-65
lines changed

client/lib/screens/sessions/ai_chat/input_user.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ class _ModelSelectorWidgetState extends ConsumerState<ModelSelectorWidget> {
333333
},
334334
),
335335
],
336+
header: OverlayMenuHeader(height: 10, child: SizedBox()), // 顶部空间
336337
footer: OverlayMenuFooter(
337338
height: 36,
338339
child: Padding(

client/lib/screens/sessions/session_operation_bar.dart

Lines changed: 144 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import 'package:client/services/sessions/session_sql_result.dart';
99
import 'package:client/services/sessions/session_conn.dart';
1010
import 'package:client/services/sessions/sessions.dart';
1111
import 'package:client/widgets/const.dart';
12+
import 'package:client/widgets/menu.dart';
1213
import 'package:client/widgets/dialog.dart';
1314
import 'package:client/widgets/button.dart';
1415
import 'package:client/widgets/divider.dart';
1516
import 'package:client/widgets/loading.dart';
17+
import 'package:client/widgets/tooltip.dart';
1618
import 'package:flutter/material.dart';
1719
import 'package:sql_parser/parser.dart';
1820
import 'package:hugeicons/hugeicons.dart';
@@ -293,78 +295,161 @@ class SchemaBar extends ConsumerStatefulWidget {
293295

294296
class _SchemaBarState extends ConsumerState<SchemaBar> {
295297
bool isEnter = false;
298+
List<String>? _schemas;
299+
late final TextEditingController _schemaSearchController;
300+
301+
@override
302+
void initState() {
303+
super.initState();
304+
_schemaSearchController = TextEditingController();
305+
}
306+
307+
@override
308+
void dispose() {
309+
_schemaSearchController.dispose();
310+
super.dispose();
311+
}
312+
313+
@override
314+
void didUpdateWidget(covariant SchemaBar oldWidget) {
315+
super.didUpdateWidget(oldWidget);
316+
if (oldWidget.instanceId != widget.instanceId) {
317+
_schemas = null;
318+
_schemaSearchController.clear();
319+
}
320+
}
321+
322+
void _onSchemaSearchChanged() {
323+
setState(() {});
324+
}
325+
326+
List<String> _filteredSchemas(List<String> schemas, String searchText) {
327+
if (searchText.isEmpty) return schemas;
328+
return schemas.where((s) => s.toLowerCase().contains(searchText.toLowerCase())).toList();
329+
}
330+
331+
Future<void> _fetchSchemas() async {
332+
if (widget.disable || widget.instanceId == null) return;
333+
if (_schemas != null) return;
334+
final schemas = await ref.read(instancesServicesProvider.notifier).getSchemas(widget.instanceId!);
335+
if (mounted) {
336+
setState(() => _schemas = schemas);
337+
}
338+
}
296339

297340
@override
298341
Widget build(BuildContext context) {
299342
final color = (isEnter && !widget.disable)
300343
? Theme.of(context).colorScheme.primary // schema 鼠标移入的颜色
301344
: Theme.of(context).colorScheme.onSurface;
302-
return Padding(
345+
346+
final schemaBarContent = Padding(
303347
padding: const EdgeInsets.symmetric(horizontal: 5),
304348
child: MouseRegion(
305-
onEnter: (_) {
306-
setState(() {
307-
isEnter = true;
308-
});
309-
},
310-
onExit: (_) {
311-
setState(() {
312-
isEnter = false;
313-
});
314-
},
315-
child: GestureDetector(
316-
onTapUp: (detail) async {
317-
if (widget.disable) {
318-
return;
319-
}
320-
final position = detail.globalPosition;
321-
final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
322-
final overlayPos = overlay.localToGlobal(Offset.zero);
323-
324-
List<String>? schemas = await ref.read(instancesServicesProvider.notifier).getSchemas(widget.instanceId!);
325-
326-
// todo
327-
showMenu(
328-
context: context,
329-
position: RelativeRect.fromLTRB(
330-
position.dx - overlayPos.dx,
331-
position.dy - overlayPos.dy,
332-
position.dx - overlayPos.dx,
333-
position.dy - overlayPos.dy,
334-
),
335-
items: schemas.map((schema) {
336-
return PopupMenuItem<String>(
337-
height: 30,
338-
onTap: () async {
339-
await ref.read(sessionConnsServicesProvider.notifier).setCurrentSchema(widget.connId!, schema);
340-
},
341-
child: Text(schema, overflow: TextOverflow.ellipsis));
342-
}).toList());
343-
},
349+
onEnter: (_) => setState(() => isEnter = true),
350+
onExit: (_) => setState(() => isEnter = false),
351+
child: Listener(
352+
onPointerDown: (_) => _fetchSchemas(),
344353
child: Container(
345-
padding: const EdgeInsets.fromLTRB(0, kSpacingTiny, 0, kSpacingTiny),
346-
child: Row(
347-
children: [
348-
HugeIcon(
349-
icon: HugeIcons.strokeRoundedDatabase,
350-
color: color,
351-
size: kIconSizeSmall,
354+
padding: const EdgeInsets.fromLTRB(0, kSpacingTiny, 0, kSpacingTiny),
355+
child: Row(
356+
children: [
357+
HugeIcon(
358+
icon: HugeIcons.strokeRoundedDatabase,
359+
color: color,
360+
size: kIconSizeSmall,
361+
),
362+
Container(
363+
padding: const EdgeInsets.only(left: kSpacingTiny),
364+
width: 120,
365+
child: Align(
366+
alignment: Alignment.centerLeft,
367+
child: Text(
368+
widget.currentSchema ?? "",
369+
overflow: TextOverflow.ellipsis,
370+
style: TextStyle(color: color),
371+
),
372+
),
373+
),
374+
],
375+
),
376+
),
377+
),
378+
),
379+
);
380+
381+
final filteredSchemas = _schemas == null ? null : _filteredSchemas(_schemas!, _schemaSearchController.text);
382+
383+
final tabs = filteredSchemas == null
384+
? [
385+
OverlayMenuItem(
386+
height: 36,
387+
child: const Center(child: Loading.medium()),
388+
),
389+
]
390+
: filteredSchemas.map((schema) {
391+
final isSelected = schema == widget.currentSchema;
392+
final color = isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface;
393+
return OverlayMenuItem(
394+
height: 30,
395+
child: Padding(
396+
padding: const EdgeInsets.symmetric(horizontal: kSpacingSmall),
397+
child: Align(
398+
alignment: Alignment.centerLeft,
399+
child: Row(
400+
children: [
401+
HugeIcon(
402+
icon: HugeIcons.strokeRoundedDatabase,
403+
color: color,
404+
size: kIconSizeSmall,
405+
),
406+
const SizedBox(width: kSpacingTiny),
407+
Expanded(
408+
child: TooltipText(text: schema, style: TextStyle(color: color)),
409+
),
410+
],
352411
),
353-
Container(
354-
padding: const EdgeInsets.only(left: kSpacingTiny),
355-
width: 120,
356-
child: Align(
357-
alignment: Alignment.centerLeft,
358-
child: Text(
359-
widget.currentSchema ?? "",
360-
overflow: TextOverflow.ellipsis,
361-
style: TextStyle(color: color),
362-
))),
363-
],
364-
)),
412+
),
413+
),
414+
onTabSelected: () async {
415+
await ref.read(sessionConnsServicesProvider.notifier).setCurrentSchema(widget.connId!, schema);
416+
},
417+
);
418+
}).toList();
419+
420+
if (widget.disable) {
421+
return schemaBarContent;
422+
}
423+
424+
final header = OverlayMenuHeader(
425+
height: 36,
426+
child: Padding(
427+
padding: const EdgeInsets.fromLTRB(kSpacingSmall, kSpacingTiny, kSpacingSmall, kSpacingTiny),
428+
child: SearchBarTheme(
429+
data: SearchBarThemeData(
430+
textStyle: WidgetStatePropertyAll(Theme.of(context).textTheme.bodySmall),
431+
backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.surfaceContainer),
432+
elevation: const WidgetStatePropertyAll(0),
433+
constraints: const BoxConstraints(minHeight: 24),
434+
),
435+
child: SearchBar(
436+
controller: _schemaSearchController,
437+
onChanged: (_) => _onSchemaSearchChanged(),
438+
trailing: const [Icon(Icons.search, size: kIconSizeSmall)],
439+
),
365440
),
366441
),
367442
);
443+
444+
return OverlayMenu(
445+
spacing: kSpacingTiny,
446+
maxHeight: 300,
447+
maxWidth: 300,
448+
tabs: tabs,
449+
header: header,
450+
footer: OverlayMenuFooter(height: kSpacingSmall, child: SizedBox()),
451+
child: schemaBarContent,
452+
);
368453
}
369454
}
370455

client/lib/services/ai/tool.dart

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,23 @@ class QueryTool extends AITool {
1919
String get name => 'execute_query';
2020

2121
@override
22-
String get description =>
23-
'在当前选中的数据库连接上执行 SQL 查询。输入 SQL 语句,返回查询结果(包括列信息和数据行)。只能执行 SELECT 查询,不能执行 INSERT、UPDATE、DELETE 等修改数据的操作。';
22+
String get description => '''
23+
Execute a SQL query on the currently selected database connection.
24+
25+
Accepts a SQL statement and returns the query result (including column metadata and rows).
26+
27+
Only SELECT queries are allowed; do not use this tool for INSERT, UPDATE, DELETE, or other data-modifying operations.
28+
29+
The result will include at most the first 100 rows.
30+
''';
2431

2532
@override
2633
Map<String, dynamic> get inputJsonSchema => {
2734
'type': 'object',
2835
'properties': {
2936
'query': {
3037
'type': 'string',
31-
'description': '要执行的 SQL 查询语句,例如:SELECT * FROM users LIMIT 10',
38+
'description': 'The SQL query string to execute, for example: SELECT * FROM users LIMIT 10',
3239
},
3340
},
3441
'required': ['query'],

client/lib/widgets/menu.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class _OverlayMenuState extends State<OverlayMenu> {
7373
borderRadius: BorderRadius.circular(12),
7474
boxShadow: [
7575
BoxShadow(
76-
color: Theme.of(context).colorScheme.surfaceContainerHigh,
76+
color: Theme.of(context).colorScheme.surfaceContainerHighest,
7777
blurRadius: 10,
7878
offset: const Offset(0, 2),
7979
),

pkg/db_driver/lib/src/db_driver_mysql.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ ORDER BY
332332

333333
@override
334334
Future<void> setCurrentSchema(String schema) async {
335-
await query("USE $schema");
335+
await query("USE `$schema`");
336336
final currentSchema = await getCurrentSchema();
337337
onSchemaChanged(currentSchema!);
338338
}

pkg/db_driver/lib/src/db_driver_pg.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@ ORDER BY
307307

308308
@override
309309
Future<void> setCurrentSchema(String schema) async {
310-
await query("SET search_path TO $schema");
310+
// 使用字符串字面量设置 search_path,避免空格等字符导致语法错误
311+
final escaped = schema.replaceAll("'", "''");
312+
await query("SET search_path TO '$escaped'");
311313
final currentSchema = await getCurrentSchema();
312314
onSchemaChanged(currentSchema!);
313315
return;

0 commit comments

Comments
 (0)