using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Interactivity;
using NewLife.Studio.AI;
using NewLife.Studio.AI.ToolCalling;
using NewLife.Studio.AI.ToolCalling.BuiltInTools;
using NewLife.Studio.AI.Models;
using NewLife.Studio.Store;
using NewLife.Studio.Data;
using NewLife.Studio.Core;
using NewLife.Studio.Core.DTOs;
using NewLife.Log;
namespace NewLife.Studio.App.Controls;
public partial class AIPanel : UserControl
{
private AIService? _aiService;
private IStoreService? _storeService;
private IDataProvider? _dataProvider;
private IDbSession? _activeSession;
private bool _configured;
public AIPanel()
{
InitializeComponent();
SendButton.Click += async (_, _) =>
{
if (_aiService == null || !_configured)
{
AddMessage("系统", "请先在设置中配置 AI 服务", Colors.Orange);
return;
}
var text = ChatInput.Text?.Trim();
if (string.IsNullOrEmpty(text)) return;
ChatInput.Text = "";
AddMessage("用户", text, Colors.DodgerBlue);
try
{
SendButton.IsEnabled = false;
var reply = await _aiService.ChatAsync(text, (tc, tr) =>
{
AddToolCall(tc, tr);
});
if (!string.IsNullOrEmpty(reply))
{
AddMessage("AI", reply, Colors.DimGray);
}
}
catch (Exception ex)
{
AddMessage("错误", ex.Message, Colors.Red);
}
finally
{
SendButton.IsEnabled = true;
}
};
}
public void Initialize(IStoreService storeService, IDataProvider dataProvider)
{
_storeService = storeService;
_dataProvider = dataProvider;
_ = LoadAiConfigAsync();
}
private async Task LoadAiConfigAsync()
{
if (_storeService == null) return;
var profile = await _storeService.GetAiProfileAsync();
if (profile != null && !string.IsNullOrEmpty(profile.ApiKey))
{
var provider = AIProviderFactory.Create(
StudioServices.GetRequiredService<HttpClient>(),
profile.ProviderType,
profile.Endpoint,
profile.ApiKey,
profile.Model);
var registry = new ToolRegistry();
RegisterBuiltInTools(registry);
_aiService = new AIService(provider, registry,
"You are a database assistant for NewLife Studio. You can analyze database structures and execute read-only queries. Always explain your analysis in the user's language.");
_configured = true;
AddMessage("系统", "AI 助手已就绪", Colors.Green);
XTrace.WriteLine("AIPanel: AI service initialized");
}
else
{
AddMessage("系统", "请先配置 AI 服务(设置 -> AI 配置)", Colors.Orange);
XTrace.WriteLine("AIPanel: AI not configured");
}
}
private void RegisterBuiltInTools(ToolRegistry registry)
{
BuiltInDatabaseTools.RegisterAll(registry,
listConnections: async () =>
{
if (_storeService == null) return "[]";
var conns = await _storeService.ListConnectionsAsync();
var names = conns.Select(c => $"{c.Name} ({c.ProviderType})");
return string.Join("\n", names);
},
openDatabase: async (name) =>
{
if (_dataProvider == null || _storeService == null) return "No data provider";
var conns = await _storeService.ListConnectionsAsync();
var conn = conns.FirstOrDefault(c => c.Name == name);
if (conn == null) return $"Connection '{name}' not found";
_activeSession = await _dataProvider.OpenSessionAsync(conn);
return $"Opened: {_activeSession.SessionId}";
},
listTables: async (_) =>
{
if (_activeSession == null) return "No active session";
var tables = await _activeSession.GetTablesAsync();
return string.Join("\n", tables.Select(t => $"{t.Name} (rows: {t.RowCount})"));
},
describeTable: async (table) =>
{
if (_activeSession == null) return "No active session";
var cols = await _activeSession.GetColumnsAsync(table);
var lines = cols.Select(c =>
$"{c.Name}: {c.DataType}{(c.IsNullable ? " NULL" : " NOT NULL")}{(c.IsPrimaryKey ? " PK" : "")}");
return string.Join("\n", lines);
},
executeSelect: async (sql) =>
{
if (_activeSession == null) return "No active session";
var result = await _activeSession.ExecuteQueryAsync(new QueryRequest
{
Sql = sql,
MaxRows = 100,
TimeoutSeconds = 30
});
if (result.Error != null) return $"Error: {result.Error}";
var lines = new List<string> { $"Columns: {string.Join(", ", result.Columns.Select(c => c.Name))}" };
lines.Add($"Rows: {result.RowCount}, Time: {result.ElapsedMs}ms");
foreach (var row in result.Rows.Take(10))
{
lines.Add(string.Join(" | ", row.Select(v => v?.ToString() ?? "NULL")));
}
if (result.Truncated) lines.Add("(results truncated)");
return string.Join("\n", lines);
},
sampleData: async (table, limit) =>
{
if (_activeSession == null) return "No active session";
var result = await _activeSession.ExecuteQueryAsync(new QueryRequest
{
Sql = $"SELECT * FROM {table} LIMIT {limit}",
MaxRows = limit,
TimeoutSeconds = 30
});
if (result.Error != null) return $"Error: {result.Error}";
return $"Sample from {table} ({result.RowCount} rows, {result.ElapsedMs}ms):\n" +
string.Join("\n", result.Rows.Select(r => string.Join(" | ", r.Select(v => v?.ToString() ?? "NULL"))));
}
);
}
private void AddMessage(string role, string content, Color color)
{
var border = new Border
{
Background = new SolidColorBrush(color, 0.1),
CornerRadius = new Avalonia.CornerRadius(4),
Padding = new Avalonia.Thickness(8),
Margin = new Avalonia.Thickness(0, 4)
};
var panel = new StackPanel();
panel.Children.Add(new TextBlock
{
Text = $"[{role}]",
FontWeight = FontWeight.Bold,
FontSize = 11,
Foreground = new SolidColorBrush(color)
});
panel.Children.Add(new TextBlock
{
Text = content,
TextWrapping = TextWrapping.Wrap,
FontSize = 12
});
border.Child = panel;
ChatHistory.Items.Add(border);
}
private void AddToolCall(ToolCall tc, ToolResult tr)
{
var expander = new Expander
{
Header = $"Tool: {tc.Function.Name}",
Margin = new Avalonia.Thickness(0, 2)
};
var content = new StackPanel();
content.Children.Add(new TextBlock
{
Text = $"Args: {tc.Function.Arguments}",
FontSize = 10,
Foreground = Brushes.Gray,
TextWrapping = TextWrapping.Wrap
});
if (tr.Error != null)
{
content.Children.Add(new TextBlock
{
Text = $"Error: {tr.Error}",
FontSize = 10,
Foreground = Brushes.Red,
TextWrapping = TextWrapping.Wrap
});
}
else
{
content.Children.Add(new TextBlock
{
Text = $"Result: {tr.Output}",
FontSize = 10,
Foreground = Brushes.DarkGreen,
TextWrapping = TextWrapping.Wrap,
MaxHeight = 200
});
}
expander.Content = new ScrollViewer { Content = content };
ChatHistory.Items.Add(expander);
}
}
|