Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Deploy GitHub Pages

on:
push:
branches:
- main
- dev_0_6_0
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: docs/site/package-lock.json

- name: Install
working-directory: docs/site
run: npm ci

- name: Build
working-directory: docs/site
env:
REPO_NAME: ${{ github.event.repository.name }}
BASE_PATH: /${{ github.event.repository.name }}/
run: npm run build

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/site/dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

49 changes: 49 additions & 0 deletions client/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -545,5 +545,54 @@
"error_directory_no_permission_macos": "No permission. Use folder button to select and authorize",
"@error_directory_no_permission_macos": {
"description": "Error message when directory has no permission on macOS"
},
"ai_chat_result_rows_returned": "Rows returned: {count}",
"@ai_chat_result_rows_returned": {
"description": "AI chat result rows returned",
"placeholders": {
"count": {
"type": "int",
"format": "decimalPattern"
}
}
},
"ai_chat_result_rows_affected": "Rows affected: {count}",
"@ai_chat_result_rows_affected": {
"description": "AI chat result rows affected",
"placeholders": {
"count": {
"type": "int",
"format": "decimalPattern"
}
}
},
"ai_chat_execution_time": "Execution time: {time}",
"@ai_chat_execution_time": {
"description": "AI chat execution time",
"placeholders": {
"time": {
"type": "String"
}
}
},
"ai_chat_tool_execute_query": "Execute Query",
"@ai_chat_tool_execute_query": {
"description": "AI chat tool execute query name"
},
"ai_chat_execution_success": "Execution successful",
"@ai_chat_execution_success": {
"description": "AI chat execution success status"
},
"ai_chat_execution_failed": "Execution failed",
"@ai_chat_execution_failed": {
"description": "AI chat execution failed status"
},
"ai_chat_thinking_process": "Thought process",
"@ai_chat_thinking_process": {
"description": "AI chat thinking process label"
},
"ai_chat_thinking": "Thinking...",
"@ai_chat_thinking": {
"description": "AI chat thinking message"
}
}
49 changes: 49 additions & 0 deletions client/lib/l10n/app_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,54 @@
"error_directory_no_permission_macos": "目录无权限,点击右侧文件夹按钮授权访问",
"@error_directory_no_permission_macos": {
"description": "macOS 目录无读写权限错误消息"
},
"ai_chat_result_rows_returned": "返回行数: {count}",
"@ai_chat_result_rows_returned": {
"description": "AI对话结果返回行数",
"placeholders": {
"count": {
"type": "int",
"format": "decimalPattern"
}
}
},
"ai_chat_result_rows_affected": "影响行数: {count}",
"@ai_chat_result_rows_affected": {
"description": "AI对话结果影响行数",
"placeholders": {
"count": {
"type": "int",
"format": "decimalPattern"
}
}
},
"ai_chat_execution_time": "执行时间: {time}",
"@ai_chat_execution_time": {
"description": "AI对话执行时间",
"placeholders": {
"time": {
"type": "String"
}
}
},
"ai_chat_tool_execute_query": "执行查询",
"@ai_chat_tool_execute_query": {
"description": "AI对话工具执行查询名称"
},
"ai_chat_execution_success": "执行成功",
"@ai_chat_execution_success": {
"description": "AI对话执行成功状态"
},
"ai_chat_execution_failed": "执行失败",
"@ai_chat_execution_failed": {
"description": "AI对话执行失败状态"
},
"ai_chat_thinking_process": "思考过程",
"@ai_chat_thinking_process": {
"description": "AI对话思考过程标签"
},
"ai_chat_thinking": "正在思考...",
"@ai_chat_thinking": {
"description": "AI对话思考消息"
}
}
143 changes: 133 additions & 10 deletions client/lib/models/ai.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import 'dart:convert';

import 'package:db_driver/db_driver.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:client/utils/state_value.dart';
import 'package:uuid/uuid.dart';

part 'ai.freezed.dart';
part 'ai.g.dart';
Expand All @@ -17,11 +22,12 @@ abstract class LLMAgentRepo {
abstract class AIChatRepo {
AIChatListModel getAIChatList();
AIChatModel create(AIChatModel model);
AIChatModel? getAIChatById(AIChatId id);
void delete(AIChatId id);
void updateMessages(AIChatId id, List<AIChatMessageModel> messages);
void updateMessages(AIChatId id, List<AIChatMessageItem> messages);
void addMessage(AIChatId id, AIChatMessageItem message);
void updateState(AIChatId id, AIChatState state);
AIChatModel? getAIChatById(AIChatId id);
void updateTables(AIChatId id, String schema, Map<String, String> tables);
void updateMessageById(AIChatId chatId, AIChatMessageId messageId, AIChatMessageItem message);
}

enum LLMAgentState {
Expand Down Expand Up @@ -91,24 +97,141 @@ abstract class AIChatId with _$AIChatId {
}) = _AIChatId;
}

@freezed
abstract class AIChatMessageId with _$AIChatMessageId {
const factory AIChatMessageId({
required String value,
}) = _AIChatMessageId;

/// 创建一个新的 AIChatMessageId,自动生成 UUID
factory AIChatMessageId.generate() => AIChatMessageId(value: const Uuid().v4());
}

@freezed
abstract class AIChatModel with _$AIChatModel {
const factory AIChatModel({
required AIChatId id,
required Map<String, Map<String, String>> tables,
required List<AIChatMessageModel> messages,
required List<AIChatMessageItem> messages,
required AIChatState state,
}) = _AIChatModel;
}

// user message
@freezed
abstract class AIChatUserMessageModel with _$AIChatUserMessageModel {
const AIChatUserMessageModel._();

const factory AIChatUserMessageModel({
required AIChatMessageId id,
required String content,
String? ref,
}) = _AIChatUserMessageModel;

String toMessage() {
final refText = ref?.trim() ?? '';
if (refText.isEmpty) return content;
// ref 作为“额外上下文”,拼到 user message 里供 LLM 使用,但 UI 仍可只展示 content。
return '$content\n\nref:\n$refText';
}
}

@freezed
abstract class AIChatMessageModel with _$AIChatMessageModel {
const factory AIChatMessageModel({
required AIRole role,
abstract class AIChatAssistantMessageModel with _$AIChatAssistantMessageModel {
const AIChatAssistantMessageModel._();

const factory AIChatAssistantMessageModel({
required AIChatMessageId id,
required String content,
String? thinking,
String? thinking, // reason_content from OpenAI API
String? error,
}) = _AIChatMessageModel;
@Default(State.running) State status,
}) = _AIChatAssistantMessageModel;

String toMessage() {
if (thinking != null && thinking!.isNotEmpty) {
return '<think>\n$thinking\n</think>\n$content';
}
return content;
}

/// 判断思考是否结束
/// 当 content 有值或者状态为完成时,思考结束
bool get isThinkingCompleted {
return content.isNotEmpty || status == State.done;
}
}

@freezed
abstract class AIChatMessageToolCallQueryModel with _$AIChatMessageToolCallQueryModel {
const factory AIChatMessageToolCallQueryModel({
required String query,
StateValue<BaseQueryResult>? result,
Duration? executeTime,
}) = _AIChatMessageToolCallQueryModel;
}

@freezed
abstract class AIChatMessageToolCallsModel with _$AIChatMessageToolCallsModel {
const AIChatMessageToolCallsModel._();

const factory AIChatMessageToolCallsModel({
required AIChatMessageId id,
required AIChatMessageToolCallQueryModel toolCall,
}) = _AIChatMessageToolCallsModel;

String toMessage() {
if (toolCall.result == null) return '';
return toolCall.result!.match(
(result) => _getSQLResultString(result) ?? '',
(error) => error,
() => '正在执行查询...',
);
}

/// 获取 SQL Result 字符串
///
/// [result] SQL 查询结果
///
/// 返回 JSON 字符串,包含查询结果的列信息和数据行
String? _getSQLResultString(BaseQueryResult result) {
try {
final data = <String, dynamic>{
'success': true,
'affectedRows': result.affectedRows.toString(),
'columns': result.columns
.map((c) => {
'name': c.name,
'type': c.dataType().name,
})
.toList(),
'rows': result.rows.map((row) {
final rowMap = <String, dynamic>{};
for (var i = 0; i < row.values.length && i < result.columns.length; i++) {
final column = result.columns[i];
final value = row.values[i];
rowMap[column.name] = value.getString();
}
return rowMap;
}).toList(),
};
final jsonString = jsonEncode(data);
return jsonString;
} catch (e) {
return jsonEncode({
'success': false,
'error': e.toString(),
});
}
}
}

/// 消息项联合类型,可以存储消息或工具调用结果
@freezed
abstract class AIChatMessageItem with _$AIChatMessageItem {
const factory AIChatMessageItem.userMessage(AIChatUserMessageModel message) = _AIChatMessageItemUserMessage;
const factory AIChatMessageItem.assistantMessage(AIChatAssistantMessageModel message) =
_AIChatMessageItemAssistantMessage;
const factory AIChatMessageItem.toolsResult(AIChatMessageToolCallsModel toolsResult) = _AIChatMessageItemToolResult;
}

@freezed
Expand Down
1 change: 1 addition & 0 deletions client/lib/models/instances.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,6 @@ abstract class PaginationInstanceListModel with _$PaginationInstanceListModel {
abstract class InstanceMetadataModel with _$InstanceMetadataModel {
const factory InstanceMetadataModel({
required List<MetaDataNode> metadata,
required String? version,
}) = _InstanceMetadataModel;
}
2 changes: 1 addition & 1 deletion client/lib/models/sessions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ abstract class SessionAIChatModel with _$SessionAIChatModel {
required SessionId sessionId,
required String? currentSchema,
required DatabaseType? dbType,
required List<MetaDataNode>? metadata,
required InstanceMetadataModel? metadata,
required ConnId? connId,
required SQLConnectState? state,
required AIChatModel chatModel,
Expand Down
Loading
Loading