Creazione progetto SynthData Pro: struttura WPF completa
Aggiunti tutti i file sorgente per la nuova applicazione desktop WPF "SynthData Pro" (namespace Dione) per la generazione dati tramite LLM locale/remoto. Inclusi: - Progetto .csproj, configurazione .NET 4.8.1, risorse e file di soluzione. - UI moderna con Material Design, sidebar, title bar custom, e navigazione tra Dashboard, Generazione Live, Impostazioni e Telemetria. - Modelli dati (AppSettings, DataProject, SchemaColumn, TelemetryLog) e layer dati SQLite con migrazione automatica. - ViewModel principali per dashboard KPI/grafici, generazione streaming, impostazioni, telemetria. - Tutte le View XAML e relativi code-behind. - Localizzazione italiana e attenzione all'usabilità. - Pronto per estensioni future (Data Designer, moduli placeholder).
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1" />
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,22 @@
|
||||
<Application x:Class="Dione.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Dione"
|
||||
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Dark.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.Blue.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Teal.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Fix ComboBox dropdown items: black text on the white popup -->
|
||||
<Style TargetType="ComboBoxItem">
|
||||
<Setter Property="Foreground" Value="Black"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Windows;
|
||||
using Dione.Data;
|
||||
|
||||
namespace Dione
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
SynthDataDbContext.EnsureCreated();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Dione.Converters
|
||||
{
|
||||
public class InverseBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool b)
|
||||
return !b;
|
||||
return false;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool b)
|
||||
return !b;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
using Dione.Models;
|
||||
|
||||
namespace Dione.Data
|
||||
{
|
||||
public static class SynthDataDbContext
|
||||
{
|
||||
private static readonly string DbPath = System.IO.Path.Combine(
|
||||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData),
|
||||
"SynthDataPro", "synthdata.db");
|
||||
|
||||
private static string ConnStr => $"Data Source={DbPath};Version=3;";
|
||||
|
||||
// ── Schema ───────────────────────────────────────────────────────────────
|
||||
|
||||
public static void EnsureCreated()
|
||||
{
|
||||
var dir = System.IO.Path.GetDirectoryName(DbPath);
|
||||
if (!System.IO.Directory.Exists(dir))
|
||||
System.IO.Directory.CreateDirectory(dir);
|
||||
if (!System.IO.File.Exists(DbPath))
|
||||
SQLiteConnection.CreateFile(DbPath);
|
||||
|
||||
using (var conn = new SQLiteConnection(ConnStr))
|
||||
{
|
||||
conn.Open();
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "CREATE TABLE IF NOT EXISTS AppSettings (" +
|
||||
"Id INTEGER PRIMARY KEY DEFAULT 1," +
|
||||
"ApiEndpoint TEXT DEFAULT 'http://127.0.0.1:1234/v1/chat/completions'," +
|
||||
"ModelName TEXT DEFAULT ''," +
|
||||
"ApiKey TEXT DEFAULT ''," +
|
||||
"Temperature REAL NOT NULL DEFAULT 0.7," +
|
||||
"MaxTokens INTEGER NOT NULL DEFAULT 2048," +
|
||||
"SystemPrompt TEXT DEFAULT ''," +
|
||||
"UserPrompt TEXT DEFAULT ''," +
|
||||
"OutputDirectory TEXT DEFAULT ''," +
|
||||
"OutputFilePrefix TEXT DEFAULT 'batch'," +
|
||||
"MaxFileSizeMb INTEGER NOT NULL DEFAULT 250," +
|
||||
"ApiTimeoutSeconds INTEGER NOT NULL DEFAULT 120," +
|
||||
"TimeoutPerTokenRatio REAL NOT NULL DEFAULT 0.5," +
|
||||
"EnableQualityVerification INTEGER NOT NULL DEFAULT 0," +
|
||||
"UseSameModelForVerification INTEGER NOT NULL DEFAULT 1," +
|
||||
"VerificationApiEndpoint TEXT DEFAULT ''," +
|
||||
"VerificationModelName TEXT DEFAULT ''," +
|
||||
"VerificationApiKey TEXT DEFAULT ''," +
|
||||
"RevenuePerHighQualityRecord REAL NOT NULL DEFAULT 0.005," +
|
||||
"ElectricityCostPerKwh REAL NOT NULL DEFAULT 0.25," +
|
||||
"SystemPowerWatt REAL NOT NULL DEFAULT 350," +
|
||||
"ApiCostType TEXT DEFAULT 'Free'," +
|
||||
"ApiCostPerCall REAL NOT NULL DEFAULT 0," +
|
||||
"ApiCostPerBlock REAL NOT NULL DEFAULT 0," +
|
||||
"ApiBlockSize INTEGER NOT NULL DEFAULT 1000" +
|
||||
");";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "CREATE TABLE IF NOT EXISTS TelemetryLogs (" +
|
||||
"Id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
"Timestamp TEXT NOT NULL," +
|
||||
"BatchId TEXT," +
|
||||
"ModelUsed TEXT," +
|
||||
"TokensPrompt INTEGER NOT NULL DEFAULT 0," +
|
||||
"TokensCompletion INTEGER NOT NULL DEFAULT 0," +
|
||||
"ExecutionTimeMs INTEGER NOT NULL DEFAULT 0," +
|
||||
"OutputPreview TEXT," +
|
||||
"ErrorMessage TEXT," +
|
||||
"IsSuccess INTEGER NOT NULL DEFAULT 0," +
|
||||
"QualityScore REAL NOT NULL DEFAULT 0," +
|
||||
"Revenue REAL NOT NULL DEFAULT 0," +
|
||||
"RecordsInBatch INTEGER NOT NULL DEFAULT 0" +
|
||||
");";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "INSERT OR IGNORE INTO AppSettings (Id) VALUES (1);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// ── Migrate TelemetryLogs: add columns if missing ──
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
var existingCols = new System.Collections.Generic.HashSet<string>();
|
||||
cmd.CommandText = "PRAGMA table_info(TelemetryLogs)";
|
||||
using (var r = cmd.ExecuteReader())
|
||||
while (r.Read())
|
||||
existingCols.Add(r.GetString(1));
|
||||
|
||||
if (!existingCols.Contains("QualityScore"))
|
||||
{ cmd.CommandText = "ALTER TABLE TelemetryLogs ADD COLUMN QualityScore REAL NOT NULL DEFAULT 0"; cmd.ExecuteNonQuery(); }
|
||||
if (!existingCols.Contains("Revenue"))
|
||||
{ cmd.CommandText = "ALTER TABLE TelemetryLogs ADD COLUMN Revenue REAL NOT NULL DEFAULT 0"; cmd.ExecuteNonQuery(); }
|
||||
if (!existingCols.Contains("RecordsInBatch"))
|
||||
{ cmd.CommandText = "ALTER TABLE TelemetryLogs ADD COLUMN RecordsInBatch INTEGER NOT NULL DEFAULT 0"; cmd.ExecuteNonQuery(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── AppSettings CRUD ─────────────────────────────────────────────────────
|
||||
|
||||
public static AppSettings LoadSettings()
|
||||
{
|
||||
EnsureCreated();
|
||||
using (var conn = new SQLiteConnection(ConnStr))
|
||||
{
|
||||
conn.Open();
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText =
|
||||
"SELECT ApiEndpoint,ModelName,ApiKey,Temperature,MaxTokens," +
|
||||
"SystemPrompt,UserPrompt,OutputDirectory,OutputFilePrefix,MaxFileSizeMb," +
|
||||
"ApiTimeoutSeconds,TimeoutPerTokenRatio," +
|
||||
"EnableQualityVerification,UseSameModelForVerification," +
|
||||
"VerificationApiEndpoint,VerificationModelName,VerificationApiKey," +
|
||||
"RevenuePerHighQualityRecord,ElectricityCostPerKwh,SystemPowerWatt," +
|
||||
"ApiCostType,ApiCostPerCall,ApiCostPerBlock,ApiBlockSize " +
|
||||
"FROM AppSettings WHERE Id=1";
|
||||
|
||||
using (var r = cmd.ExecuteReader())
|
||||
{
|
||||
if (r.Read())
|
||||
return new AppSettings
|
||||
{
|
||||
ApiEndpoint = r.GetValue(0)?.ToString() ?? "",
|
||||
ModelName = r.GetValue(1)?.ToString() ?? "",
|
||||
ApiKey = r.GetValue(2)?.ToString() ?? "",
|
||||
Temperature = r.IsDBNull(3) ? 0.7 : r.GetDouble(3),
|
||||
MaxTokens = r.IsDBNull(4) ? 2048 : r.GetInt32(4),
|
||||
SystemPrompt = r.GetValue(5)?.ToString() ?? "",
|
||||
UserPrompt = r.GetValue(6)?.ToString() ?? "",
|
||||
OutputDirectory = r.GetValue(7)?.ToString() ?? "",
|
||||
OutputFilePrefix = r.GetValue(8)?.ToString() ?? "batch",
|
||||
MaxFileSizeMb = r.IsDBNull(9) ? 250 : r.GetInt32(9),
|
||||
ApiTimeoutSeconds = r.IsDBNull(10) ? 120 : r.GetInt32(10),
|
||||
TimeoutPerTokenRatio = r.IsDBNull(11) ? 0.5 : r.GetDouble(11),
|
||||
EnableQualityVerification = !r.IsDBNull(12) && r.GetInt32(12) != 0,
|
||||
UseSameModelForVerification = r.IsDBNull(13) || r.GetInt32(13) != 0,
|
||||
VerificationApiEndpoint = r.GetValue(14)?.ToString() ?? "",
|
||||
VerificationModelName = r.GetValue(15)?.ToString() ?? "",
|
||||
VerificationApiKey = r.GetValue(16)?.ToString() ?? "",
|
||||
RevenuePerHighQualityRecord = r.IsDBNull(17) ? 0.005 : r.GetDouble(17),
|
||||
ElectricityCostPerKwh = r.IsDBNull(18) ? 0.25 : r.GetDouble(18),
|
||||
SystemPowerWatt = r.IsDBNull(19) ? 350 : r.GetDouble(19),
|
||||
ApiCostType = r.GetValue(20)?.ToString() ?? "Free",
|
||||
ApiCostPerCall = r.IsDBNull(21) ? 0 : r.GetDouble(21),
|
||||
ApiCostPerBlock = r.IsDBNull(22) ? 0 : r.GetDouble(22),
|
||||
ApiBlockSize = r.IsDBNull(23) ? 1000 : r.GetInt32(23),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return new AppSettings();
|
||||
}
|
||||
|
||||
public static void SaveSettings(AppSettings s)
|
||||
{
|
||||
EnsureCreated();
|
||||
using (var conn = new SQLiteConnection(ConnStr))
|
||||
{
|
||||
conn.Open();
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText =
|
||||
"UPDATE AppSettings SET " +
|
||||
"ApiEndpoint=@api,ModelName=@model,ApiKey=@apikey," +
|
||||
"Temperature=@temp,MaxTokens=@mt," +
|
||||
"SystemPrompt=@sp,UserPrompt=@up," +
|
||||
"OutputDirectory=@odir,OutputFilePrefix=@opfx,MaxFileSizeMb=@mfsz," +
|
||||
"ApiTimeoutSeconds=@timeout,TimeoutPerTokenRatio=@tokratio," +
|
||||
"EnableQualityVerification=@eqv,UseSameModelForVerification=@usesame," +
|
||||
"VerificationApiEndpoint=@vapi,VerificationModelName=@vmodel,VerificationApiKey=@vapikey," +
|
||||
"RevenuePerHighQualityRecord=@rev," +
|
||||
"ElectricityCostPerKwh=@ecost,SystemPowerWatt=@spow," +
|
||||
"ApiCostType=@actype,ApiCostPerCall=@acall,ApiCostPerBlock=@ablock,ApiBlockSize=@ablk " +
|
||||
"WHERE Id=1";
|
||||
cmd.Parameters.AddWithValue("@api", s.ApiEndpoint);
|
||||
cmd.Parameters.AddWithValue("@model", s.ModelName);
|
||||
cmd.Parameters.AddWithValue("@apikey", s.ApiKey);
|
||||
cmd.Parameters.AddWithValue("@temp", s.Temperature);
|
||||
cmd.Parameters.AddWithValue("@mt", s.MaxTokens);
|
||||
cmd.Parameters.AddWithValue("@sp", s.SystemPrompt);
|
||||
cmd.Parameters.AddWithValue("@up", s.UserPrompt);
|
||||
cmd.Parameters.AddWithValue("@odir", s.OutputDirectory);
|
||||
cmd.Parameters.AddWithValue("@opfx", s.OutputFilePrefix);
|
||||
cmd.Parameters.AddWithValue("@mfsz", s.MaxFileSizeMb);
|
||||
cmd.Parameters.AddWithValue("@timeout", s.ApiTimeoutSeconds);
|
||||
cmd.Parameters.AddWithValue("@tokratio", s.TimeoutPerTokenRatio);
|
||||
cmd.Parameters.AddWithValue("@eqv", s.EnableQualityVerification ? 1 : 0);
|
||||
cmd.Parameters.AddWithValue("@usesame", s.UseSameModelForVerification ? 1 : 0);
|
||||
cmd.Parameters.AddWithValue("@vapi", s.VerificationApiEndpoint);
|
||||
cmd.Parameters.AddWithValue("@vmodel", s.VerificationModelName);
|
||||
cmd.Parameters.AddWithValue("@vapikey", s.VerificationApiKey);
|
||||
cmd.Parameters.AddWithValue("@rev", s.RevenuePerHighQualityRecord);
|
||||
cmd.Parameters.AddWithValue("@ecost", s.ElectricityCostPerKwh);
|
||||
cmd.Parameters.AddWithValue("@spow", s.SystemPowerWatt);
|
||||
cmd.Parameters.AddWithValue("@actype", s.ApiCostType);
|
||||
cmd.Parameters.AddWithValue("@acall", s.ApiCostPerCall);
|
||||
cmd.Parameters.AddWithValue("@ablock", s.ApiCostPerBlock);
|
||||
cmd.Parameters.AddWithValue("@ablk", s.ApiBlockSize);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Telemetry ────────────────────────────────────────────────────────────
|
||||
|
||||
public static List<TelemetryLog> QueryAll()
|
||||
{
|
||||
var results = new List<TelemetryLog>();
|
||||
if (!System.IO.File.Exists(DbPath)) return results;
|
||||
using (var conn = new SQLiteConnection(ConnStr))
|
||||
{
|
||||
conn.Open();
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText =
|
||||
"SELECT Id,Timestamp,BatchId,ModelUsed,TokensPrompt,TokensCompletion," +
|
||||
"ExecutionTimeMs,OutputPreview,ErrorMessage,IsSuccess " +
|
||||
"FROM TelemetryLogs ORDER BY Id DESC LIMIT 1000";
|
||||
using (var r = cmd.ExecuteReader())
|
||||
{
|
||||
while (r.Read())
|
||||
results.Add(new TelemetryLog
|
||||
{
|
||||
Id = r.GetInt32(0),
|
||||
Timestamp = DateTime.TryParse(r.GetValue(1)?.ToString(), out var ts) ? ts : default,
|
||||
BatchId = r.GetValue(2)?.ToString(),
|
||||
ModelUsed = r.GetValue(3)?.ToString(),
|
||||
TokensPrompt = r.IsDBNull(4) ? 0 : r.GetInt32(4),
|
||||
TokensCompletion = r.IsDBNull(5) ? 0 : r.GetInt32(5),
|
||||
ExecutionTimeMs = r.IsDBNull(6) ? 0 : r.GetInt64(6),
|
||||
OutputPreview = r.GetValue(7)?.ToString(),
|
||||
ErrorMessage = r.GetValue(8)?.ToString(),
|
||||
IsSuccess = !r.IsDBNull(9) && r.GetInt32(9) != 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public static void InsertLog(TelemetryLog log)
|
||||
{
|
||||
EnsureCreated();
|
||||
using (var conn = new SQLiteConnection(ConnStr))
|
||||
{
|
||||
conn.Open();
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText =
|
||||
"INSERT INTO TelemetryLogs " +
|
||||
"(Timestamp,BatchId,ModelUsed,TokensPrompt,TokensCompletion," +
|
||||
"ExecutionTimeMs,OutputPreview,ErrorMessage,IsSuccess,QualityScore,Revenue,RecordsInBatch) " +
|
||||
"VALUES (@ts,@bid,@model,@tp,@tc,@et,@op,@err,@ok,0,0,0)";
|
||||
cmd.Parameters.AddWithValue("@ts", log.Timestamp.ToString("o"));
|
||||
cmd.Parameters.AddWithValue("@bid", (object)log.BatchId ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@model", (object)log.ModelUsed ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@tp", log.TokensPrompt);
|
||||
cmd.Parameters.AddWithValue("@tc", log.TokensCompletion);
|
||||
cmd.Parameters.AddWithValue("@et", log.ExecutionTimeMs);
|
||||
cmd.Parameters.AddWithValue("@op", (object)log.OutputPreview ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@err", (object)log.ErrorMessage ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@ok", log.IsSuccess ? 1 : 0);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Reset ────────────────────────────────────────────────────────────────
|
||||
|
||||
public static void DeleteAllTelemetry()
|
||||
{
|
||||
if (!System.IO.File.Exists(DbPath)) return;
|
||||
using (var conn = new SQLiteConnection(ConnStr))
|
||||
{
|
||||
conn.Open();
|
||||
using (var cmd = conn.CreateCommand()) { cmd.CommandText = "DELETE FROM TelemetryLogs;"; cmd.ExecuteNonQuery(); }
|
||||
}
|
||||
}
|
||||
|
||||
public static void ResetDatabase()
|
||||
{
|
||||
if (System.IO.File.Exists(DbPath)) System.IO.File.Delete(DbPath);
|
||||
EnsureCreated();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{C455F7DB-C47E-4D71-87D6-9C122F18F986}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>Dione</RootNamespace>
|
||||
<AssemblyName>Dione</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xaml">
|
||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System.Data.SQLite">
|
||||
<HintPath>C:\Users\alber\.nuget\packages\stub.system.data.sqlite.core.netframework\1.0.119\lib\net46\System.Data.SQLite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SQLite.EF6">
|
||||
<HintPath>C:\Users\alber\.nuget\packages\system.data.sqlite.ef6\1.0.119\lib\net46\System.Data.SQLite.EF6.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CommunityToolkit.Mvvm">
|
||||
<HintPath>C:\Users\alber\.nuget\packages\communitytoolkit.mvvm\8.4.0\lib\netstandard2.0\CommunityToolkit.Mvvm.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="EntityFramework">
|
||||
<HintPath>C:\Users\alber\.nuget\packages\entityframework\6.5.1\lib\net45\EntityFramework.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>C:\Users\alber\.nuget\packages\newtonsoft.json\13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="LiveCharts">
|
||||
<HintPath>C:\Users\alber\.nuget\packages\livecharts\0.9.7\lib\net45\LiveCharts.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="LiveCharts.Wpf">
|
||||
<HintPath>C:\Users\alber\.nuget\packages\livecharts.wpf\0.9.7\lib\net45\LiveCharts.Wpf.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EntityFramework" Version="6.5.1" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.119.0" />
|
||||
<PackageReference Include="System.Data.SQLite.EF6" Version="1.0.119.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
|
||||
<PackageReference Include="LiveCharts.Wpf" Version="0.9.7" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="Converters\InverseBoolConverter.cs" />
|
||||
<Compile Include="Models\AppSettings.cs" />
|
||||
<Compile Include="ViewModels\LiveGenerationViewModel.cs" />
|
||||
<Compile Include="ViewModels\TelemetryHistoryViewModel.cs" />
|
||||
<Compile Include="Views\DataDesignerView.xaml.cs">
|
||||
<DependentUpon>DataDesignerView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\LiveGenerationView.xaml.cs">
|
||||
<DependentUpon>LiveGenerationView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\SettingsView.xaml.cs">
|
||||
<DependentUpon>SettingsView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\TelemetryHistoryView.xaml.cs">
|
||||
<DependentUpon>TelemetryHistoryView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Page Include="Views\DashboardView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Views\DataDesignerView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\LiveGenerationView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\PlaceholderView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Data\SynthDataDbContext.cs" />
|
||||
<Compile Include="Dione\Models\DataProject.cs" />
|
||||
<Compile Include="Dione\Models\SchemaColumn.cs" />
|
||||
<Compile Include="Dione\Models\TelemetryLog.cs" />
|
||||
<Compile Include="Dione\ViewModels\MainWindowViewModel.cs" />
|
||||
<Compile Include="Dione\Views\DashboardView.xaml.cs" />
|
||||
<Compile Include="Dione\Views\PlaceholderView.xaml.cs" />
|
||||
<Compile Include="Models\DataProject.cs" />
|
||||
<Compile Include="Models\SchemaColumn.cs" />
|
||||
<Compile Include="Models\TelemetryLog.cs" />
|
||||
<Compile Include="ViewModels\DashboardViewModel.cs" />
|
||||
<Compile Include="ViewModels\MainWindowViewModel.cs" />
|
||||
<Compile Include="ViewModels\SettingsViewModel.cs" />
|
||||
<Compile Include="Views\DashboardView.xaml.cs">
|
||||
<DependentUpon>DashboardView.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Views\PlaceholderView.xaml.cs">
|
||||
<DependentUpon>PlaceholderView.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MainWindow.xaml.cs">
|
||||
<DependentUpon>MainWindow.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Page Include="Views\SettingsView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\TelemetryHistoryView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Include="Dione\Dione_new.csproj" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="Dione.csproj" Id="c455f7db-c47e-4d71-87d6-9c122f18f986" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project>
|
||||
<!-- Fix for WPF _wpftmp project not resolving PackageReference assemblies -->
|
||||
<Target Name="FixWpfTmpProjectPackageReferences"
|
||||
BeforeTargets="MarkupCompilePass1;MarkupCompilePass2"
|
||||
Condition="$(MSBuildProjectFile.Contains('_wpftmp'))">
|
||||
<ItemGroup>
|
||||
<ReferencePath Include="$(NuGetPackageRoot)communitytoolkit.mvvm\8.4.0\lib\netstandard2.0\CommunityToolkit.Mvvm.dll" />
|
||||
<ReferencePath Include="$(NuGetPackageRoot)entityframework\6.5.1\lib\net45\EntityFramework.dll" />
|
||||
<ReferencePath Include="$(NuGetPackageRoot)stub.system.data.sqlite.core.netframework\1.0.119\lib\net46\System.Data.SQLite.dll" />
|
||||
<ReferencePath Include="$(NuGetPackageRoot)system.data.sqlite.ef6\1.0.119\lib\net46\System.Data.SQLite.EF6.dll" />
|
||||
<ReferencePath Include="$(NuGetPackageRoot)newtonsoft.json\13.0.3\lib\net45\Newtonsoft.Json.dll" />
|
||||
<ReferencePath Include="$(NuGetPackageRoot)livecharts\0.9.7\lib\net45\LiveCharts.dll" />
|
||||
<ReferencePath Include="$(NuGetPackageRoot)livecharts.wpf\0.9.7\lib\net45\LiveCharts.Wpf.dll" />
|
||||
<ReferencePath Include="$(FrameworkPathOverride)\System.Windows.Forms.dll" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,182 @@
|
||||
<Window x:Class="Dione.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Dione"
|
||||
xmlns:vm="clr-namespace:Dione.ViewModels"
|
||||
xmlns:views="clr-namespace:Dione.Views"
|
||||
mc:Ignorable="d"
|
||||
Title="SynthData Pro"
|
||||
Height="720" Width="1280"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
ResizeMode="CanResizeWithGrip"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
UseLayoutRounding="True"
|
||||
SnapsToDevicePixels="True"
|
||||
TextOptions.TextFormattingMode="Display">
|
||||
|
||||
<Window.DataContext>
|
||||
<vm:MainWindowViewModel/>
|
||||
</Window.DataContext>
|
||||
|
||||
<Window.Resources>
|
||||
<DataTemplate DataType="{x:Type vm:DashboardViewModel}">
|
||||
<views:DashboardView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
|
||||
<views:SettingsView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type vm:LiveGenerationViewModel}">
|
||||
<views:LiveGenerationView/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type vm:TelemetryHistoryViewModel}">
|
||||
<views:TelemetryHistoryView/>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Sidebar button style -->
|
||||
<Style x:Key="SidebarBtn" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="#78909C"/>
|
||||
<Setter Property="FontSize" Value="13"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
||||
<Setter Property="Padding" Value="20,12"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}" CornerRadius="8" Margin="6,2">
|
||||
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#2A2A3E"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Border CornerRadius="12" Background="#121218" BorderBrush="#2A2A3E" BorderThickness="1">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="220"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
<Border Grid.Column="0" Background="#16161E" CornerRadius="12,0,0,12">
|
||||
<DockPanel>
|
||||
<!-- App Title Bar -->
|
||||
<Border DockPanel.Dock="Top" Padding="16,14" MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||
<StackPanel>
|
||||
<TextBlock Text="SynthData Pro" FontSize="18" FontWeight="Bold" Foreground="White"/>
|
||||
<TextBlock Text="Local LLM Data Generator" FontSize="10" Foreground="#546E7A" Margin="0,2,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Separator DockPanel.Dock="Top" Background="#2A2A3E" Margin="16,0"/>
|
||||
|
||||
<!-- Navigation -->
|
||||
<StackPanel DockPanel.Dock="Top" Margin="0,8,0,0">
|
||||
<Button Style="{StaticResource SidebarBtn}" Command="{Binding NavigateCommand}" CommandParameter="Dashboard">
|
||||
<StackPanel Orientation="Horizontal"><TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16" VerticalAlignment="Center" Margin="0,0,10,0"/><TextBlock Text="Dashboard" VerticalAlignment="Center"/></StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource SidebarBtn}" Command="{Binding NavigateCommand}" CommandParameter="LiveGeneration">
|
||||
<StackPanel Orientation="Horizontal"><TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16" VerticalAlignment="Center" Margin="0,0,10,0"/><TextBlock Text="Generazione" VerticalAlignment="Center"/></StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource SidebarBtn}" Command="{Binding NavigateCommand}" CommandParameter="Telemetry">
|
||||
<StackPanel Orientation="Horizontal"><TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16" VerticalAlignment="Center" Margin="0,0,10,0"/><TextBlock Text="Telemetria" VerticalAlignment="Center"/></StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Settings at bottom -->
|
||||
<Button DockPanel.Dock="Bottom" Style="{StaticResource SidebarBtn}" Command="{Binding NavigateCommand}" CommandParameter="Settings" Margin="0,0,0,8">
|
||||
<StackPanel Orientation="Horizontal"><TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16" VerticalAlignment="Center" Margin="0,0,10,0"/><TextBlock Text="Impostazioni" VerticalAlignment="Center"/></StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Version footer -->
|
||||
<TextBlock DockPanel.Dock="Bottom" Text="v1.0.0-alpha" FontSize="10"
|
||||
Foreground="#37474F" HorizontalAlignment="Center" Margin="0,0,0,12"/>
|
||||
<Border/> <!-- spacer -->
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<DockPanel Grid.Column="1">
|
||||
<!-- Custom Title Bar -->
|
||||
<Border DockPanel.Dock="Top" Height="40" Background="#16161E"
|
||||
CornerRadius="0,12,0,0" MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||
<DockPanel LastChildFill="False">
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="0,0,8,0">
|
||||
<Button Content="" FontFamily="Segoe MDL2 Assets" Width="40" Height="28" FontSize="10"
|
||||
Command="{Binding MinimizeCommand}" Cursor="Hand">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="Transparent" CornerRadius="4">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#33FFFFFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Button.Foreground>
|
||||
<SolidColorBrush Color="#B0BEC5"/>
|
||||
</Button.Foreground>
|
||||
</Button>
|
||||
<Button Content="" FontFamily="Segoe MDL2 Assets" Width="40" Height="28" FontSize="10"
|
||||
Command="{Binding MaximizeCommand}" Cursor="Hand">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="Transparent" CornerRadius="4">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#33FFFFFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Button.Foreground>
|
||||
<SolidColorBrush Color="#B0BEC5"/>
|
||||
</Button.Foreground>
|
||||
</Button>
|
||||
<Button Content="" FontFamily="Segoe MDL2 Assets" Width="40" Height="28" FontSize="10"
|
||||
Command="{Binding CloseCommand}" Cursor="Hand">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="Transparent" CornerRadius="4">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#C62828"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<Button.Foreground>
|
||||
<SolidColorBrush Color="#EF5350"/>
|
||||
</Button.Foreground>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Content Area -->
|
||||
<ContentControl Content="{Binding CurrentView}" Margin="0"/>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Dione
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
SourceInitialized += OnSourceInitialized;
|
||||
}
|
||||
|
||||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ClickCount == 2)
|
||||
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||
else
|
||||
DragMove();
|
||||
}
|
||||
|
||||
private void OnSourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
var handle = new WindowInteropHelper(this).Handle;
|
||||
HwndSource.FromHwnd(handle)?.AddHook(WndProc);
|
||||
}
|
||||
|
||||
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
// WM_GETMINMAXINFO
|
||||
if (msg == 0x0024)
|
||||
{
|
||||
var mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam);
|
||||
|
||||
var monitor = MonitorFromWindow(hwnd, 0x00000002 /* MONITOR_DEFAULTTONEAREST */);
|
||||
if (monitor != IntPtr.Zero)
|
||||
{
|
||||
var monitorInfo = new MONITORINFO { cbSize = Marshal.SizeOf<MONITORINFO>() };
|
||||
if (GetMonitorInfo(monitor, ref monitorInfo))
|
||||
{
|
||||
var work = monitorInfo.rcWork;
|
||||
var area = monitorInfo.rcMonitor;
|
||||
|
||||
mmi.ptMaxPosition.x = work.left - area.left;
|
||||
mmi.ptMaxPosition.y = work.top - area.top;
|
||||
mmi.ptMaxSize.x = work.right - work.left;
|
||||
mmi.ptMaxSize.y = work.bottom - work.top;
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.StructureToPtr(mmi, lParam, true);
|
||||
handled = true;
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct POINT { public int x, y; }
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT { public int left, top, right, bottom; }
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MINMAXINFO
|
||||
{
|
||||
public POINT ptReserved;
|
||||
public POINT ptMaxSize;
|
||||
public POINT ptMaxPosition;
|
||||
public POINT ptMaxTrackSize;
|
||||
public POINT ptMinTrackSize;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MONITORINFO
|
||||
{
|
||||
public int cbSize;
|
||||
public RECT rcMonitor;
|
||||
public RECT rcWork;
|
||||
public uint dwFlags;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
namespace Dione.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Configurazione globale dell'applicazione — sempre un solo record (Id = 1).
|
||||
/// </summary>
|
||||
public class AppSettings
|
||||
{
|
||||
public int Id { get; set; } = 1;
|
||||
|
||||
// ?? API principale ??
|
||||
public string ApiEndpoint { get; set; } = "http://127.0.0.1:1234/v1/chat/completions";
|
||||
public string ModelName { get; set; } = "";
|
||||
public string ApiKey { get; set; } = "";
|
||||
public double Temperature { get; set; } = 0.7;
|
||||
public int MaxTokens { get; set; } = 2048;
|
||||
|
||||
// ?? Prompt ??
|
||||
public string SystemPrompt { get; set; } = "";
|
||||
public string UserPrompt { get; set; } = "";
|
||||
|
||||
// ?? Output ??
|
||||
public string OutputDirectory { get; set; } = "";
|
||||
public string OutputFilePrefix { get; set; } = "batch";
|
||||
public int MaxFileSizeMb { get; set; } = 250;
|
||||
|
||||
// ?? Timeout ??
|
||||
public int ApiTimeoutSeconds { get; set; } = 120;
|
||||
public double TimeoutPerTokenRatio { get; set; } = 0.5;
|
||||
|
||||
// ?? Verifica qualità ??
|
||||
public bool EnableQualityVerification { get; set; } = false;
|
||||
public bool UseSameModelForVerification { get; set; } = true;
|
||||
public string VerificationApiEndpoint { get; set; } = "";
|
||||
public string VerificationModelName { get; set; } = "";
|
||||
public string VerificationApiKey { get; set; } = "";
|
||||
public double RevenuePerHighQualityRecord { get; set; } = 0.005;
|
||||
|
||||
// ?? Costi ??
|
||||
public double ElectricityCostPerKwh { get; set; } = 0.25;
|
||||
public double SystemPowerWatt { get; set; } = 350;
|
||||
public string ApiCostType { get; set; } = "Free";
|
||||
public double ApiCostPerCall { get; set; } = 0.0;
|
||||
public double ApiCostPerBlock { get; set; } = 0.0;
|
||||
public int ApiBlockSize { get; set; } = 1000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Dione.Models
|
||||
{
|
||||
[Table("DataProjects")]
|
||||
public class DataProject
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required, MaxLength(200)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[MaxLength(512)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[MaxLength(512)]
|
||||
public string ApiEndpoint { get; set; } = "http://127.0.0.1:1234/v1/";
|
||||
|
||||
[MaxLength(128)]
|
||||
public string ModelName { get; set; }
|
||||
|
||||
/// <summary>API Key per servizi remoti (OpenAI, Anthropic, Google AI, etc.)</summary>
|
||||
[MaxLength(512)]
|
||||
public string ApiKey { get; set; } = "";
|
||||
|
||||
public double Temperature { get; set; } = 0.7;
|
||||
|
||||
public int MaxTokens { get; set; } = 2048;
|
||||
|
||||
[MaxLength(16)]
|
||||
public string OutputFormat { get; set; } = "JSON";
|
||||
|
||||
public string SystemPrompt { get; set; }
|
||||
|
||||
public string FewShotExamples { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime? LastRunAt { get; set; }
|
||||
|
||||
public int RecordsPerFile { get; set; } = 100;
|
||||
|
||||
[MaxLength(512)]
|
||||
public string OutputDirectory { get; set; } = "";
|
||||
|
||||
[MaxLength(256)]
|
||||
public string OutputFileName { get; set; } = "fin_ticks";
|
||||
|
||||
public bool ContinuousMode { get; set; } = true;
|
||||
|
||||
public int MinFieldCount { get; set; } = 120;
|
||||
|
||||
[MaxLength(16)]
|
||||
public string FieldSeparator { get; set; } = "|";
|
||||
|
||||
// ?? Cost Management ??
|
||||
/// <summary>Costo elettricità in €/kWh</summary>
|
||||
public double ElectricityCostPerKwh { get; set; } = 0.25;
|
||||
|
||||
/// <summary>Potenza stimata del sistema in Watt durante generazione</summary>
|
||||
public double SystemPowerWatt { get; set; } = 350;
|
||||
|
||||
/// <summary>Costo API AI per chiamata (€)</summary>
|
||||
public double ApiCostPerCall { get; set; } = 0.0;
|
||||
|
||||
/// <summary>Costo API AI per blocco di N chiamate (€)</summary>
|
||||
public double ApiCostPerBlock { get; set; } = 0.0;
|
||||
|
||||
/// <summary>Numero chiamate per blocco tariffario</summary>
|
||||
public int ApiBlockSize { get; set; } = 1000;
|
||||
|
||||
/// <summary>Tipo tariffazione: PerCall, PerBlock, Free</summary>
|
||||
[MaxLength(16)]
|
||||
public string ApiCostType { get; set; } = "Free";
|
||||
|
||||
// ?? Revenue / Verification ??
|
||||
/// <summary>Endpoint AI di verifica (può essere lo stesso o diverso)</summary>
|
||||
[MaxLength(512)]
|
||||
public string VerificationApiEndpoint { get; set; } = "";
|
||||
|
||||
/// <summary>Modello AI per verifica qualità</summary>
|
||||
[MaxLength(128)]
|
||||
public string VerificationModelName { get; set; } = "";
|
||||
|
||||
/// <summary>API Key per servizio verifica qualità</summary>
|
||||
[MaxLength(512)]
|
||||
public string VerificationApiKey { get; set; } = "";
|
||||
|
||||
/// <summary>Se true, usa stesso endpoint/modello/key della generazione per la verifica</summary>
|
||||
public bool UseSameModelForVerification { get; set; } = true;
|
||||
|
||||
/// <summary>Abilita verifica qualità automatica</summary>
|
||||
public bool EnableQualityVerification { get; set; } = false;
|
||||
|
||||
/// <summary>Valore base per record di alta qualità (€)</summary>
|
||||
public double RevenuePerHighQualityRecord { get; set; } = 0.005;
|
||||
|
||||
/// <summary>Timeout base per chiamata API in secondi</summary>
|
||||
public int ApiTimeoutSeconds { get; set; } = 120;
|
||||
|
||||
/// <summary>Timeout aggiuntivo per token (secondi per ogni 100 token)</summary>
|
||||
public double TimeoutPerTokenRatio { get; set; } = 0.5;
|
||||
|
||||
public virtual ICollection<SchemaColumn> SchemaColumns { get; set; } = new List<SchemaColumn>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Dione.Models
|
||||
{
|
||||
[Table("SchemaColumns")]
|
||||
public class SchemaColumn
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int DataProjectId { get; set; }
|
||||
|
||||
[Required, MaxLength(128)]
|
||||
public string ColumnName { get; set; }
|
||||
|
||||
[Required, MaxLength(64)]
|
||||
public string DataType { get; set; }
|
||||
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
[ForeignKey(nameof(DataProjectId))]
|
||||
public virtual DataProject DataProject { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Dione.Models
|
||||
{
|
||||
[Table("TelemetryLogs")]
|
||||
public class TelemetryLog
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[MaxLength(64)]
|
||||
public string BatchId { get; set; }
|
||||
|
||||
public int Seed { get; set; }
|
||||
|
||||
public string Prompt { get; set; }
|
||||
|
||||
[MaxLength(128)]
|
||||
public string ModelUsed { get; set; }
|
||||
|
||||
public int TokensPrompt { get; set; }
|
||||
|
||||
public int TokensCompletion { get; set; }
|
||||
|
||||
public long ExecutionTimeMs { get; set; }
|
||||
|
||||
[MaxLength(1000)]
|
||||
public string OutputPreview { get; set; }
|
||||
|
||||
[MaxLength(2000)]
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public bool IsSuccess { get; set; }
|
||||
}
|
||||
}
|
||||
Generated
+71
@@ -0,0 +1,71 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// Codice generato da uno strumento.
|
||||
// Versione runtime:4.0.30319.42000
|
||||
//
|
||||
// Le modifiche apportate a questo file possono causare un comportamento non corretto e andranno perse se
|
||||
// il codice viene rigenerato.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Dione.Properties
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Classe di risorse fortemente tipizzata per la ricerca di stringhe localizzate e così via.
|
||||
/// </summary>
|
||||
// Questa classe è stata generata automaticamente dalla classe StronglyTypedResourceBuilder
|
||||
// tramite uno strumento quale ResGen o Visual Studio.
|
||||
// Per aggiungere o rimuovere un membro, modificare il file .ResX, quindi eseguire di nuovo ResGen
|
||||
// con l'opzione /str oppure ricompilare il progetto VS.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restituisce l'istanza di ResourceManager memorizzata nella cache e usata da questa classe.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dione.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue l'override della proprietà CurrentUICulture del thread corrente per tutte
|
||||
/// le ricerche di risorse che utilizzano questa classe di risorse fortemente tipizzata.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
Generated
+30
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Dione.Properties
|
||||
{
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LiveCharts;
|
||||
using LiveCharts.Wpf;
|
||||
using Dione.Data;
|
||||
|
||||
namespace Dione.ViewModels
|
||||
{
|
||||
public class DashboardViewModel : ObservableObject
|
||||
{
|
||||
private long _totalTokensSpent;
|
||||
public long TotalTokensSpent
|
||||
{
|
||||
get => _totalTokensSpent;
|
||||
set => SetProperty(ref _totalTokensSpent, value);
|
||||
}
|
||||
|
||||
private int _validRecords;
|
||||
public int ValidRecords
|
||||
{
|
||||
get => _validRecords;
|
||||
set => SetProperty(ref _validRecords, value);
|
||||
}
|
||||
|
||||
private double _estimatedCostEur;
|
||||
public double EstimatedCostEur
|
||||
{
|
||||
get => _estimatedCostEur;
|
||||
set => SetProperty(ref _estimatedCostEur, value);
|
||||
}
|
||||
|
||||
private double _qualityScore;
|
||||
public double QualityScore
|
||||
{
|
||||
get => _qualityScore;
|
||||
set => SetProperty(ref _qualityScore, value);
|
||||
}
|
||||
|
||||
private SeriesCollection _tokensPerMinuteSeries;
|
||||
public SeriesCollection TokensPerMinuteSeries
|
||||
{
|
||||
get => _tokensPerMinuteSeries;
|
||||
set => SetProperty(ref _tokensPerMinuteSeries, value);
|
||||
}
|
||||
|
||||
private string[] _tokensPerMinuteLabels;
|
||||
public string[] TokensPerMinuteLabels
|
||||
{
|
||||
get => _tokensPerMinuteLabels;
|
||||
set => SetProperty(ref _tokensPerMinuteLabels, value);
|
||||
}
|
||||
|
||||
private SeriesCollection _successErrorSeries;
|
||||
public SeriesCollection SuccessErrorSeries
|
||||
{
|
||||
get => _successErrorSeries;
|
||||
set => SetProperty(ref _successErrorSeries, value);
|
||||
}
|
||||
|
||||
private SeriesCollection _latencySeries;
|
||||
public SeriesCollection LatencySeries
|
||||
{
|
||||
get => _latencySeries;
|
||||
set => SetProperty(ref _latencySeries, value);
|
||||
}
|
||||
|
||||
private string[] _latencyLabels;
|
||||
public string[] LatencyLabels
|
||||
{
|
||||
get => _latencyLabels;
|
||||
set => SetProperty(ref _latencyLabels, value);
|
||||
}
|
||||
|
||||
public RelayCommand RefreshCommand { get; }
|
||||
|
||||
public DashboardViewModel()
|
||||
{
|
||||
RefreshCommand = new RelayCommand(RefreshFromDb);
|
||||
|
||||
// Initialize with empty series
|
||||
TokensPerMinuteSeries = new SeriesCollection
|
||||
{
|
||||
new LineSeries { Title = "Tokens/min", Values = new ChartValues<double>() }
|
||||
};
|
||||
TokensPerMinuteLabels = Array.Empty<string>();
|
||||
|
||||
SuccessErrorSeries = new SeriesCollection
|
||||
{
|
||||
new PieSeries { Title = "Success", Values = new ChartValues<double> { 0 }, DataLabels = true },
|
||||
new PieSeries { Title = "API Error", Values = new ChartValues<double> { 0 }, DataLabels = true },
|
||||
new PieSeries { Title = "Parse Fail", Values = new ChartValues<double> { 0 }, DataLabels = true }
|
||||
};
|
||||
|
||||
LatencySeries = new SeriesCollection
|
||||
{
|
||||
new ColumnSeries { Title = "Latency (ms)", Values = new ChartValues<double>() }
|
||||
};
|
||||
LatencyLabels = Array.Empty<string>();
|
||||
|
||||
RefreshFromDb();
|
||||
}
|
||||
|
||||
public void RefreshFromDb()
|
||||
{
|
||||
try
|
||||
{
|
||||
var logs = SynthDataDbContext.QueryAll();
|
||||
|
||||
if (logs.Count == 0)
|
||||
{
|
||||
TotalTokensSpent = 0;
|
||||
ValidRecords = 0;
|
||||
EstimatedCostEur = 0;
|
||||
QualityScore = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// --- KPI Cards ---
|
||||
long totalPrompt = logs.Sum(l => l.TokensPrompt);
|
||||
long totalCompletion = logs.Sum(l => l.TokensCompletion);
|
||||
TotalTokensSpent = totalPrompt + totalCompletion;
|
||||
|
||||
int successCount = logs.Count(l => l.IsSuccess);
|
||||
ValidRecords = successCount;
|
||||
|
||||
// Estimate electric cost: ~0.3 kWh GPU idle, rough 0.0003 EUR/token
|
||||
double totalSeconds = logs.Sum(l => l.ExecutionTimeMs) / 1000.0;
|
||||
double kWh = (totalSeconds / 3600.0) * 0.3;
|
||||
EstimatedCostEur = Math.Round(kWh * 0.25, 4);
|
||||
|
||||
int totalCount = logs.Count;
|
||||
QualityScore = totalCount > 0
|
||||
? Math.Round(100.0 * successCount / totalCount, 1)
|
||||
: 0;
|
||||
|
||||
// --- Tokens per minute (group by minute) ---
|
||||
var byMinute = logs
|
||||
.Where(l => l.Timestamp != default)
|
||||
.GroupBy(l => new DateTime(l.Timestamp.Year, l.Timestamp.Month, l.Timestamp.Day,
|
||||
l.Timestamp.Hour, l.Timestamp.Minute, 0))
|
||||
.OrderBy(g => g.Key)
|
||||
.ToList();
|
||||
|
||||
var tpmValues = new ChartValues<double>();
|
||||
var tpmLabels = new List<string>();
|
||||
foreach (var g in byMinute)
|
||||
{
|
||||
tpmValues.Add(g.Sum(x => x.TokensPrompt + x.TokensCompletion));
|
||||
tpmLabels.Add(g.Key.ToString("HH:mm"));
|
||||
}
|
||||
|
||||
TokensPerMinuteSeries = new SeriesCollection
|
||||
{
|
||||
new LineSeries
|
||||
{
|
||||
Title = "Tokens/min",
|
||||
Values = tpmValues,
|
||||
PointGeometry = null
|
||||
}
|
||||
};
|
||||
TokensPerMinuteLabels = tpmLabels.ToArray();
|
||||
|
||||
// --- Success vs Error pie ---
|
||||
int apiErrors = logs.Count(l => !l.IsSuccess && !string.IsNullOrEmpty(l.ErrorMessage)
|
||||
&& l.ErrorMessage.IndexOf("parse", StringComparison.OrdinalIgnoreCase) < 0);
|
||||
int parseErrors = logs.Count(l => !l.IsSuccess && !string.IsNullOrEmpty(l.ErrorMessage)
|
||||
&& l.ErrorMessage.IndexOf("parse", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
int otherErrors = totalCount - successCount - apiErrors - parseErrors;
|
||||
apiErrors += otherErrors;
|
||||
|
||||
SuccessErrorSeries = new SeriesCollection
|
||||
{
|
||||
new PieSeries { Title = "Success", Values = new ChartValues<double> { successCount }, DataLabels = true },
|
||||
new PieSeries { Title = "API Error", Values = new ChartValues<double> { apiErrors }, DataLabels = true },
|
||||
new PieSeries { Title = "Parse Fail", Values = new ChartValues<double> { parseErrors }, DataLabels = true }
|
||||
};
|
||||
|
||||
// --- Latency bar chart (last 20 requests) ---
|
||||
var recent = logs.OrderByDescending(l => l.Timestamp).Take(20).Reverse().ToList();
|
||||
var latValues = new ChartValues<double>(recent.Select(l => (double)l.ExecutionTimeMs));
|
||||
var latLabels = recent.Select((l, i) => $"#{i + 1}").ToArray();
|
||||
|
||||
LatencySeries = new SeriesCollection
|
||||
{
|
||||
new ColumnSeries { Title = "Latency (ms)", Values = latValues }
|
||||
};
|
||||
LatencyLabels = latLabels;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// DB not yet initialized or empty - leave zeros
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Dione.Data;
|
||||
using Dione.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Dione.ViewModels
|
||||
{
|
||||
public class LogEntry
|
||||
{
|
||||
public string Level { get; set; }
|
||||
public string Message { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string TimeLabel => Timestamp.ToString("HH:mm:ss");
|
||||
}
|
||||
|
||||
public class LiveGenerationViewModel : ObservableObject
|
||||
{
|
||||
// HttpClient senza timeout fisso: lo gestiamo per-request con CancellationToken
|
||||
private static readonly HttpClient Http = new HttpClient { Timeout = Timeout.InfiniteTimeSpan };
|
||||
|
||||
// ── Observable properties ─────────────────────────────────────────────
|
||||
|
||||
private bool _isRunning;
|
||||
public bool IsRunning { get => _isRunning; set { SetProperty(ref _isRunning, value); StartCommand.NotifyCanExecuteChanged(); StopCommand.NotifyCanExecuteChanged(); } }
|
||||
|
||||
private string _statusText = "Pronto";
|
||||
public string StatusText { get => _statusText; set => SetProperty(ref _statusText, value); }
|
||||
|
||||
private string _elapsedTime = "00:00:00";
|
||||
public string ElapsedTime { get => _elapsedTime; set => SetProperty(ref _elapsedTime, value); }
|
||||
|
||||
private int _completedBatches;
|
||||
public int CompletedBatches { get => _completedBatches; set => SetProperty(ref _completedBatches, value); }
|
||||
|
||||
private int _successCount;
|
||||
public int SuccessCount { get => _successCount; set => SetProperty(ref _successCount, value); }
|
||||
|
||||
private int _errorCount;
|
||||
public int ErrorCount { get => _errorCount; set => SetProperty(ref _errorCount, value); }
|
||||
|
||||
private long _totalRecordsWritten;
|
||||
public long TotalRecordsWritten { get => _totalRecordsWritten; set => SetProperty(ref _totalRecordsWritten, value); }
|
||||
|
||||
private int _filesCreated;
|
||||
public int FilesCreated { get => _filesCreated; set => SetProperty(ref _filesCreated, value); }
|
||||
|
||||
private string _currentFileName = "";
|
||||
public string CurrentFileName { get => _currentFileName; set => SetProperty(ref _currentFileName, value); }
|
||||
|
||||
private long _currentFileSizeBytes;
|
||||
public long CurrentFileSizeBytes { get => _currentFileSizeBytes; set => SetProperty(ref _currentFileSizeBytes, value); }
|
||||
|
||||
private double _currentFileSizeMb;
|
||||
public double CurrentFileSizeMb { get => _currentFileSizeMb; set => SetProperty(ref _currentFileSizeMb, value); }
|
||||
|
||||
private string _lastPreview = "";
|
||||
public string LastPreview { get => _lastPreview; set => SetProperty(ref _lastPreview, value); }
|
||||
|
||||
private double _totalCostElectricity;
|
||||
public double TotalCostElectricity { get => _totalCostElectricity; set => SetProperty(ref _totalCostElectricity, value); }
|
||||
|
||||
private double _totalCostApi;
|
||||
public double TotalCostApi { get => _totalCostApi; set => SetProperty(ref _totalCostApi, value); }
|
||||
|
||||
private double _totalRevenue;
|
||||
public double TotalRevenue { get => _totalRevenue; set => SetProperty(ref _totalRevenue, value); }
|
||||
|
||||
private double _netProfit;
|
||||
public double NetProfit { get => _netProfit; set => SetProperty(ref _netProfit, value); }
|
||||
|
||||
private double _avgBatchTimeMs;
|
||||
public double AvgBatchTimeMs { get => _avgBatchTimeMs; set => SetProperty(ref _avgBatchTimeMs, value); }
|
||||
|
||||
private int _tokensTotal;
|
||||
public int TokensTotal { get => _tokensTotal; set => SetProperty(ref _tokensTotal, value); }
|
||||
|
||||
// Usato come Maximum dalla ProgressBar del file corrente
|
||||
private double _maxFileSizeMbDouble = 250;
|
||||
public double MaxFileSizeMbDouble { get => _maxFileSizeMbDouble; set => SetProperty(ref _maxFileSizeMbDouble, value); }
|
||||
|
||||
// ── Streaming ─────────────────────────────────────────────────────────
|
||||
private string _streamingText = "";
|
||||
public string StreamingText { get => _streamingText; set => SetProperty(ref _streamingText, value); }
|
||||
|
||||
private bool _isStreaming;
|
||||
public bool IsStreaming { get => _isStreaming; set => SetProperty(ref _isStreaming, value); }
|
||||
|
||||
private int _streamingChars;
|
||||
public int StreamingChars { get => _streamingChars; set => SetProperty(ref _streamingChars, value); }
|
||||
|
||||
private int _streamingTokensLive;
|
||||
public int StreamingTokensLive { get => _streamingTokensLive; set => SetProperty(ref _streamingTokensLive, value); }
|
||||
|
||||
public ObservableCollection<LogEntry> LogEntries { get; } = new ObservableCollection<LogEntry>();
|
||||
|
||||
// ── Commands ──────────────────────────────────────────────────────────
|
||||
|
||||
public RelayCommand StartCommand { get; }
|
||||
public RelayCommand StopCommand { get; }
|
||||
public RelayCommand ClearLogCommand { get; }
|
||||
public RelayCommand CopyLogsCommand { get; }
|
||||
|
||||
// ── Private fields ────────────────────────────────────────────────────
|
||||
|
||||
private CancellationTokenSource _cts;
|
||||
private Stopwatch _sessionSw;
|
||||
private DispatcherTimer _elapsedTimer;
|
||||
|
||||
// ── Constructor ───────────────────────────────────────────────────────
|
||||
|
||||
public LiveGenerationViewModel()
|
||||
{
|
||||
StartCommand = new RelayCommand(StartGeneration, () => !IsRunning);
|
||||
StopCommand = new RelayCommand(StopGeneration, () => IsRunning);
|
||||
ClearLogCommand = new RelayCommand(() => LogEntries.Clear());
|
||||
CopyLogsCommand = new RelayCommand(CopyLogs, () => LogEntries.Count > 0);
|
||||
|
||||
_elapsedTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
_elapsedTimer.Tick += (_, __) =>
|
||||
{
|
||||
if (_sessionSw == null) return;
|
||||
var e = _sessionSw.Elapsed;
|
||||
ElapsedTime = $"{(int)e.TotalHours:D2}:{e.Minutes:D2}:{e.Seconds:D2}";
|
||||
};
|
||||
}
|
||||
|
||||
// ── Generation ────────────────────────────────────────────────────────
|
||||
|
||||
private async void StartGeneration()
|
||||
{
|
||||
var settings = SynthDataDbContext.LoadSettings();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.ApiEndpoint)) { Log("ERR", "API Endpoint non configurato."); return; }
|
||||
if (string.IsNullOrWhiteSpace(settings.SystemPrompt)) { Log("ERR", "System Prompt vuoto."); return; }
|
||||
if (string.IsNullOrWhiteSpace(settings.UserPrompt)) { Log("ERR", "User Prompt vuoto."); return; }
|
||||
if (string.IsNullOrWhiteSpace(settings.OutputDirectory))
|
||||
{
|
||||
Log("ERR", "Cartella output non configurata. Vai nelle Impostazioni.");
|
||||
return;
|
||||
}
|
||||
if (!Directory.Exists(settings.OutputDirectory))
|
||||
{
|
||||
try { Directory.CreateDirectory(settings.OutputDirectory); }
|
||||
catch { Log("ERR", "Impossibile creare la cartella output."); return; }
|
||||
}
|
||||
|
||||
// Reset counters
|
||||
IsRunning = true;
|
||||
CompletedBatches = 0;
|
||||
SuccessCount = 0;
|
||||
ErrorCount = 0;
|
||||
TotalRecordsWritten = 0;
|
||||
FilesCreated = 0;
|
||||
CurrentFileSizeBytes = 0;
|
||||
CurrentFileSizeMb = 0;
|
||||
TotalCostElectricity = 0;
|
||||
TotalCostApi = 0;
|
||||
TotalRevenue = 0;
|
||||
NetProfit = 0;
|
||||
AvgBatchTimeMs = 0;
|
||||
TokensTotal = 0;
|
||||
LastPreview = "";
|
||||
CurrentFileName = "";
|
||||
ElapsedTime = "00:00:00";
|
||||
_sessionSw = Stopwatch.StartNew();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
_elapsedTimer.Start();
|
||||
|
||||
// Derived settings
|
||||
var endpoint = settings.ApiEndpoint.Trim();
|
||||
var apiKey = settings.ApiKey ?? "";
|
||||
bool isLocal = IsLocalEndpoint(endpoint);
|
||||
long maxBytes = (long)settings.MaxFileSizeMb * 1024 * 1024;
|
||||
int calcTimeout = (int)(settings.ApiTimeoutSeconds + settings.MaxTokens * settings.TimeoutPerTokenRatio / 100.0);
|
||||
MaxFileSizeMbDouble = settings.MaxFileSizeMb;
|
||||
|
||||
Log("INFO", $"Endpoint: {endpoint} | Model: {settings.ModelName}");
|
||||
Log("INFO", $"Output: {settings.OutputDirectory} | Max file: {settings.MaxFileSizeMb} MB");
|
||||
Log("INFO", $"Timeout: {calcTimeout}s | Max tokens: {settings.MaxTokens}");
|
||||
if (!string.IsNullOrWhiteSpace(apiKey) && !isLocal)
|
||||
Log("INFO", "Autenticazione API: attiva");
|
||||
else if (!string.IsNullOrWhiteSpace(apiKey) && isLocal)
|
||||
Log("WARN", "API Key ignorata (endpoint locale)");
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
// ── File state ──
|
||||
string filePath = null;
|
||||
int fileIndex = NextFileIndex(settings.OutputDirectory, settings.OutputFilePrefix);
|
||||
|
||||
var batchSw = new Stopwatch();
|
||||
long totalBatchMs = 0;
|
||||
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
// ── Open new file if needed ──
|
||||
if (filePath == null || !File.Exists(filePath) || new FileInfo(filePath).Length >= maxBytes)
|
||||
{
|
||||
filePath = Path.Combine(settings.OutputDirectory, $"{settings.OutputFilePrefix}_{fileIndex:D3}.jsonl");
|
||||
fileIndex++;
|
||||
UpdateUI(() => { FilesCreated++; CurrentFileName = Path.GetFileName(filePath); CurrentFileSizeBytes = 0; CurrentFileSizeMb = 0; });
|
||||
Log("INFO", $"[NUOVO FILE] {Path.GetFileName(filePath)}");
|
||||
}
|
||||
|
||||
// ── Build request (streaming) ──
|
||||
var requestBody = new
|
||||
{
|
||||
model = settings.ModelName ?? "",
|
||||
messages = new[]
|
||||
{
|
||||
new { role = "system", content = settings.SystemPrompt },
|
||||
new { role = "user", content = settings.UserPrompt }
|
||||
},
|
||||
temperature = settings.Temperature,
|
||||
max_tokens = settings.MaxTokens,
|
||||
stream = true,
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(requestBody);
|
||||
var telelog = new TelemetryLog
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
BatchId = Guid.NewGuid().ToString("N").Substring(0, 12),
|
||||
ModelUsed = settings.ModelName,
|
||||
};
|
||||
|
||||
// Reset streaming UI state
|
||||
UpdateUI(() => { StreamingText = ""; StreamingChars = 0; StreamingTokensLive = 0; IsStreaming = false; });
|
||||
|
||||
batchSw.Restart();
|
||||
HttpResponseMessage response = null;
|
||||
int retries = 0;
|
||||
|
||||
while (retries < 3 && response == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var content = new StringContent(json, Encoding.UTF8, "application/json"))
|
||||
using (var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(calcTimeout)))
|
||||
using (var linked = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutCts.Token))
|
||||
{
|
||||
var req = new HttpRequestMessage(HttpMethod.Post, endpoint) { Content = content };
|
||||
if (!string.IsNullOrWhiteSpace(apiKey) && !isLocal)
|
||||
req.Headers.Add("Authorization", $"Bearer {apiKey}");
|
||||
|
||||
// ResponseHeadersRead: leggiamo il body in streaming
|
||||
response = await Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, linked.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (!token.IsCancellationRequested)
|
||||
{
|
||||
retries++;
|
||||
Log("WARN", $"Timeout, tentativo {retries}/3");
|
||||
if (retries < 3) await DelayAsync(2000, token);
|
||||
}
|
||||
catch (Exception ex) when (!token.IsCancellationRequested)
|
||||
{
|
||||
retries++;
|
||||
var msg = ex.Message + (ex.InnerException != null ? " | " + ex.InnerException.Message : "");
|
||||
Log("ERR", $"Errore connessione: {msg}");
|
||||
if (retries < 3) await DelayAsync(2000, token);
|
||||
}
|
||||
}
|
||||
|
||||
batchSw.Stop();
|
||||
|
||||
if (token.IsCancellationRequested) break;
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
telelog.IsSuccess = false;
|
||||
telelog.ErrorMessage = "Tutti i retry esauriti.";
|
||||
SynthDataDbContext.InsertLog(telelog);
|
||||
UpdateUI(() => ErrorCount++);
|
||||
await DelayAsync(3000, token);
|
||||
continue;
|
||||
}
|
||||
|
||||
telelog.ExecutionTimeMs = batchSw.ElapsedMilliseconds;
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
string errBody = "";
|
||||
try { errBody = await response.Content.ReadAsStringAsync(); } catch { }
|
||||
Log("ERR", $"HTTP {(int)response.StatusCode} {response.ReasonPhrase}");
|
||||
if (!string.IsNullOrWhiteSpace(errBody))
|
||||
Log("ERR", $"Body: {errBody.Substring(0, Math.Min(500, errBody.Length))}");
|
||||
var err = $"HTTP {(int)response.StatusCode}: {errBody?.Substring(0, Math.Min(200, errBody?.Length ?? 0))}";
|
||||
telelog.IsSuccess = false;
|
||||
telelog.ErrorMessage = err;
|
||||
SynthDataDbContext.InsertLog(telelog);
|
||||
UpdateUI(() => ErrorCount++);
|
||||
await DelayAsync(2000, token);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── Leggi lo stream SSE ──
|
||||
string generatedText = null;
|
||||
int tokensPrompt = 0;
|
||||
int tokensComp = 0;
|
||||
|
||||
try
|
||||
{
|
||||
var accumulated = new StringBuilder();
|
||||
UpdateUI(() => IsStreaming = true);
|
||||
|
||||
using (var httpStream = await response.Content.ReadAsStreamAsync())
|
||||
using (var reader = new StreamReader(httpStream))
|
||||
{
|
||||
string line;
|
||||
while ((line = await reader.ReadLineAsync()) != null)
|
||||
{
|
||||
if (token.IsCancellationRequested) break;
|
||||
if (string.IsNullOrEmpty(line)) continue;
|
||||
if (!line.StartsWith("data:")) continue;
|
||||
|
||||
var payload = line.Substring(5).TrimStart();
|
||||
if (payload == "[DONE]") break;
|
||||
|
||||
try
|
||||
{
|
||||
var chunk = JObject.Parse(payload);
|
||||
|
||||
// Aggiorna usage se presente nel chunk (alcuni provider lo inviano nell'ultimo chunk)
|
||||
var usageNode = chunk["usage"];
|
||||
if (usageNode != null)
|
||||
{
|
||||
tokensPrompt = usageNode["prompt_tokens"]?.Value<int>() ?? tokensPrompt;
|
||||
tokensComp = usageNode["completion_tokens"]?.Value<int>() ?? tokensComp;
|
||||
}
|
||||
|
||||
var delta = chunk["choices"]?[0]?["delta"]?["content"]?.Value<string>();
|
||||
if (!string.IsNullOrEmpty(delta))
|
||||
{
|
||||
accumulated.Append(delta);
|
||||
tokensComp++; // stima locale se il provider non invia usage inline
|
||||
var snapshot = accumulated.ToString();
|
||||
UpdateUI(() =>
|
||||
{
|
||||
StreamingText = snapshot;
|
||||
StreamingChars = snapshot.Length;
|
||||
StreamingTokensLive = tokensComp;
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception chunkEx)
|
||||
{
|
||||
Log("WARN", $"Chunk SSE non parsabile: {chunkEx.Message}");
|
||||
Log("WARN", $"Payload: {payload.Substring(0, Math.Min(200, payload.Length))}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generatedText = accumulated.ToString();
|
||||
UpdateUI(() => { IsStreaming = false; LastPreview = generatedText.Substring(0, Math.Min(400, generatedText.Length)); });
|
||||
}
|
||||
catch (Exception ex) when (!token.IsCancellationRequested)
|
||||
{
|
||||
UpdateUI(() => { IsStreaming = false; });
|
||||
Log("ERR", "Lettura stream fallita: " + ex.Message);
|
||||
UpdateUI(() => ErrorCount++);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(generatedText))
|
||||
{
|
||||
Log("WARN", "Stream vuoto: nessun testo ricevuto dall'API.");
|
||||
if (!string.IsNullOrEmpty(generatedText))
|
||||
Log("WARN", $"Contenuto parziale: {generatedText.Substring(0, Math.Min(300, generatedText.Length))}");
|
||||
UpdateUI(() => ErrorCount++);
|
||||
continue;
|
||||
}
|
||||
|
||||
telelog.TokensPrompt = tokensPrompt;
|
||||
telelog.TokensCompletion = tokensComp;
|
||||
|
||||
// ── Rimuovi blocchi <think> e log se presenti ──
|
||||
var strippedText = StripThinkingBlocks(generatedText);
|
||||
if (strippedText.Length != generatedText.Length)
|
||||
Log("INFO", $"Rimossi blocchi <think>: {generatedText.Length - strippedText.Length} caratteri di ragionamento ignorati.");
|
||||
generatedText = strippedText;
|
||||
|
||||
// ── Extract JSON array from text (handles markdown code blocks) ──
|
||||
var jsonArray = ExtractJsonArray(generatedText);
|
||||
if (jsonArray == null)
|
||||
{
|
||||
Log("WARN", "Nessun array JSON trovato nella risposta.");
|
||||
Log("WARN", $"Risposta grezza ({generatedText.Length} car): {generatedText.Substring(0, Math.Min(500, generatedText.Length))}");
|
||||
if (generatedText.Length > 500)
|
||||
Log("WARN", $"...fine risposta: {generatedText.Substring(generatedText.Length - Math.Min(200, generatedText.Length))}");
|
||||
UpdateUI(() => ErrorCount++);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── Write JSONL ──
|
||||
int recordsInBatch = 0;
|
||||
var sb = new StringBuilder();
|
||||
foreach (var obj in jsonArray)
|
||||
{
|
||||
sb.AppendLine(obj.ToString(Formatting.None));
|
||||
recordsInBatch++;
|
||||
}
|
||||
|
||||
var jsonlChunk = sb.ToString();
|
||||
File.AppendAllText(filePath, jsonlChunk, Encoding.UTF8);
|
||||
|
||||
// ── Cost calculation ──
|
||||
double elecCost = (batchSw.Elapsed.TotalHours * settings.SystemPowerWatt / 1000.0) * settings.ElectricityCostPerKwh;
|
||||
double apiCost = 0;
|
||||
if (settings.ApiCostType == "PerCall") apiCost = settings.ApiCostPerCall;
|
||||
if (settings.ApiCostType == "PerBlock") apiCost = (1.0 / Math.Max(1, settings.ApiBlockSize)) * settings.ApiCostPerBlock;
|
||||
|
||||
// ── Update state ──
|
||||
totalBatchMs += batchSw.ElapsedMilliseconds;
|
||||
telelog.IsSuccess = true;
|
||||
telelog.OutputPreview = jsonlChunk.Substring(0, Math.Min(300, jsonlChunk.Length));
|
||||
SynthDataDbContext.InsertLog(telelog);
|
||||
|
||||
long newSize = new FileInfo(filePath).Length;
|
||||
|
||||
UpdateUI(() =>
|
||||
{
|
||||
CompletedBatches++;
|
||||
SuccessCount++;
|
||||
TotalRecordsWritten += recordsInBatch;
|
||||
TokensTotal += tokensPrompt + tokensComp;
|
||||
TotalCostElectricity += elecCost;
|
||||
TotalCostApi += apiCost;
|
||||
NetProfit = TotalRevenue - TotalCostApi - TotalCostElectricity;
|
||||
CurrentFileSizeBytes = newSize;
|
||||
CurrentFileSizeMb = newSize / (1024.0 * 1024.0);
|
||||
AvgBatchTimeMs = CompletedBatches > 0 ? (double)totalBatchMs / CompletedBatches : 0;
|
||||
LastPreview = jsonlChunk.Substring(0, Math.Min(400, jsonlChunk.Length));
|
||||
});
|
||||
|
||||
Log("OK", $"Batch #{CompletedBatches}: {recordsInBatch} record | {tokensPrompt + tokensComp} tok | {batchSw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
Log("INFO", $"Generazione fermata. Batch: {CompletedBatches}, Record: {TotalRecordsWritten}, Errori: {ErrorCount}");
|
||||
UpdateUI(() => { IsRunning = false; StatusText = "Fermato"; _elapsedTimer.Stop(); });
|
||||
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_elapsedTimer.Stop();
|
||||
Log("INFO", $"Generazione interrotta. Batch: {CompletedBatches}, Record: {TotalRecordsWritten}");
|
||||
UpdateUI(() => { IsRunning = false; StatusText = "Fermato"; });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_elapsedTimer.Stop();
|
||||
Log("ERR", $"Errore imprevisto: {ex.Message}");
|
||||
UpdateUI(() => { IsRunning = false; StatusText = "Errore"; });
|
||||
}
|
||||
}
|
||||
|
||||
private void StopGeneration()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
StatusText = "Arresto in corso...";
|
||||
Log("WARN", "Stop richiesto...");
|
||||
}
|
||||
|
||||
private void CopyLogs()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (int i = LogEntries.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var e = LogEntries[i];
|
||||
sb.AppendLine($"{e.TimeLabel} [{e.Level,-4}] {e.Message}");
|
||||
}
|
||||
System.Windows.Clipboard.SetText(sb.ToString());
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
private static bool IsLocalEndpoint(string url)
|
||||
=> url.Contains("localhost") || url.Contains("127.0.0.1")
|
||||
|| url.Contains("172.") || url.Contains("192.168.") || url.Contains("10.");
|
||||
|
||||
/// <summary>Attende ms millisecondi; non lancia eccezione se il token viene cancellato.</summary>
|
||||
private static async Task DelayAsync(int ms, CancellationToken token)
|
||||
{
|
||||
try { await Task.Delay(ms, token); }
|
||||
catch (OperationCanceledException) { /* attesa interrotta: normale al Stop */ }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Estrae il primo array JSON dalla risposta (gestisce anche ```json ... ``` markdown).
|
||||
/// </summary>
|
||||
private static string StripThinkingBlocks(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text)) return text;
|
||||
// Tag di ragionamento usati da vari modelli
|
||||
var tagPairs = new[] { ("<think>", "</think>"), ("<thought>", "</thought>") };
|
||||
foreach (var (open, close) in tagPairs)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
int i = 0;
|
||||
while (i < text.Length)
|
||||
{
|
||||
int openIdx = text.IndexOf(open, i, StringComparison.OrdinalIgnoreCase);
|
||||
if (openIdx < 0) { sb.Append(text, i, text.Length - i); break; }
|
||||
sb.Append(text, i, openIdx - i);
|
||||
int closeIdx = text.IndexOf(close, openIdx, StringComparison.OrdinalIgnoreCase);
|
||||
i = closeIdx < 0 ? text.Length : closeIdx + close.Length;
|
||||
}
|
||||
text = sb.ToString();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static JArray ExtractJsonArray(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text)) return null;
|
||||
|
||||
// Rimuovi blocchi di ragionamento <think>...</think>
|
||||
text = StripThinkingBlocks(text);
|
||||
|
||||
// Strip markdown code fences
|
||||
var cleaned = text.Trim();
|
||||
if (cleaned.StartsWith("```"))
|
||||
{
|
||||
var firstNewline = cleaned.IndexOf('\n');
|
||||
var lastFence = cleaned.LastIndexOf("```");
|
||||
if (firstNewline > 0 && lastFence > firstNewline)
|
||||
cleaned = cleaned.Substring(firstNewline + 1, lastFence - firstNewline - 1).Trim();
|
||||
}
|
||||
|
||||
// Find first [ ... ]
|
||||
var start = cleaned.IndexOf('[');
|
||||
var end = cleaned.LastIndexOf(']');
|
||||
if (start < 0 || end <= start) return null;
|
||||
|
||||
try { return JArray.Parse(cleaned.Substring(start, end - start + 1)); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
private static int NextFileIndex(string dir, string prefix)
|
||||
{
|
||||
int max = 0;
|
||||
if (!Directory.Exists(dir)) return 1;
|
||||
foreach (var f in Directory.GetFiles(dir, $"{prefix}_*.jsonl"))
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(f);
|
||||
var part = name.Replace(prefix + "_", "");
|
||||
if (int.TryParse(part, out var n) && n > max) max = n;
|
||||
}
|
||||
return max + 1;
|
||||
}
|
||||
|
||||
private void Log(string level, string msg)
|
||||
{
|
||||
UpdateUI(() =>
|
||||
{
|
||||
LogEntries.Insert(0, new LogEntry { Level = level, Message = msg, Timestamp = DateTime.Now });
|
||||
if (LogEntries.Count > 500) LogEntries.RemoveAt(LogEntries.Count - 1);
|
||||
CopyLogsCommand?.NotifyCanExecuteChanged();
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateUI(Action action)
|
||||
{
|
||||
if (System.Windows.Application.Current?.Dispatcher == null) { action(); return; }
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
action();
|
||||
else
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Dione.ViewModels
|
||||
{
|
||||
public class MainWindowViewModel : ObservableObject
|
||||
{
|
||||
private object _currentView;
|
||||
public object CurrentView
|
||||
{
|
||||
get => _currentView;
|
||||
set => SetProperty(ref _currentView, value);
|
||||
}
|
||||
|
||||
private string _selectedMenu = "Dashboard";
|
||||
public string SelectedMenu
|
||||
{
|
||||
get => _selectedMenu;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedMenu, value))
|
||||
NavigateTo(value);
|
||||
}
|
||||
}
|
||||
|
||||
public DashboardViewModel DashboardVm { get; }
|
||||
public SettingsViewModel SettingsVm { get; }
|
||||
public LiveGenerationViewModel LiveGenerationVm { get; }
|
||||
public TelemetryHistoryViewModel TelemetryHistoryVm { get; }
|
||||
|
||||
public RelayCommand MinimizeCommand { get; }
|
||||
public RelayCommand MaximizeCommand { get; }
|
||||
public RelayCommand CloseCommand { get; }
|
||||
public RelayCommand<string> NavigateCommand { get; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
DashboardVm = new DashboardViewModel();
|
||||
SettingsVm = new SettingsViewModel();
|
||||
LiveGenerationVm = new LiveGenerationViewModel();
|
||||
TelemetryHistoryVm = new TelemetryHistoryViewModel();
|
||||
|
||||
CurrentView = DashboardVm;
|
||||
|
||||
MinimizeCommand = new RelayCommand(() =>
|
||||
System.Windows.Application.Current.MainWindow.WindowState = System.Windows.WindowState.Minimized);
|
||||
MaximizeCommand = new RelayCommand(() =>
|
||||
{
|
||||
var w = System.Windows.Application.Current.MainWindow;
|
||||
w.WindowState = w.WindowState == System.Windows.WindowState.Maximized
|
||||
? System.Windows.WindowState.Normal
|
||||
: System.Windows.WindowState.Maximized;
|
||||
});
|
||||
CloseCommand = new RelayCommand(() => System.Windows.Application.Current.Shutdown());
|
||||
NavigateCommand = new RelayCommand<string>(NavigateTo);
|
||||
}
|
||||
|
||||
private void NavigateTo(string view)
|
||||
{
|
||||
switch (view)
|
||||
{
|
||||
case "Dashboard":
|
||||
DashboardVm.RefreshFromDb();
|
||||
CurrentView = DashboardVm;
|
||||
break;
|
||||
case "LiveGeneration":
|
||||
CurrentView = LiveGenerationVm;
|
||||
break;
|
||||
case "Settings":
|
||||
CurrentView = SettingsVm;
|
||||
break;
|
||||
case "Telemetry":
|
||||
CurrentView = TelemetryHistoryVm;
|
||||
break;
|
||||
}
|
||||
SelectedMenu = view;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Dione.Data;
|
||||
using Dione.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Dione.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : ObservableObject
|
||||
{
|
||||
// ── API ──────────────────────────────────────────────────────────────────
|
||||
|
||||
private string _selectedEndpointPreset = "Custom";
|
||||
public string SelectedEndpointPreset
|
||||
{
|
||||
get => _selectedEndpointPreset;
|
||||
set { if (SetProperty(ref _selectedEndpointPreset, value) && value != "Custom") ApplyPreset(value); }
|
||||
}
|
||||
public string[] EndpointPresets { get; } = { "Custom", "OpenAI", "Anthropic", "Google AI", "Azure OpenAI", "LM Studio (Local)", "Ollama (Local)" };
|
||||
|
||||
private string _apiEndpoint = "http://127.0.0.1:1234/v1/chat/completions";
|
||||
public string ApiEndpoint { get => _apiEndpoint; set => SetProperty(ref _apiEndpoint, value); }
|
||||
|
||||
private string _modelName = "";
|
||||
public string ModelName { get => _modelName; set => SetProperty(ref _modelName, value); }
|
||||
|
||||
private string _apiKey = "";
|
||||
public string ApiKey { get => _apiKey; set => SetProperty(ref _apiKey, value); }
|
||||
|
||||
private double _temperature = 0.7;
|
||||
public double Temperature { get => _temperature; set => SetProperty(ref _temperature, value); }
|
||||
|
||||
private int _maxTokens = 2048;
|
||||
public int MaxTokens { get => _maxTokens; set => SetProperty(ref _maxTokens, value); }
|
||||
|
||||
// ── Prompt ───────────────────────────────────────────────────────────────
|
||||
|
||||
private string _systemPrompt = "";
|
||||
public string SystemPrompt { get => _systemPrompt; set => SetProperty(ref _systemPrompt, value); }
|
||||
|
||||
private string _userPrompt = "";
|
||||
public string UserPrompt { get => _userPrompt; set => SetProperty(ref _userPrompt, value); }
|
||||
|
||||
// ── Output ───────────────────────────────────────────────────────────────
|
||||
|
||||
private string _outputDirectory = "";
|
||||
public string OutputDirectory { get => _outputDirectory; set => SetProperty(ref _outputDirectory, value); }
|
||||
|
||||
private string _outputFilePrefix = "batch";
|
||||
public string OutputFilePrefix { get => _outputFilePrefix; set => SetProperty(ref _outputFilePrefix, value); }
|
||||
|
||||
private int _maxFileSizeMb = 250;
|
||||
public int MaxFileSizeMb { get => _maxFileSizeMb; set => SetProperty(ref _maxFileSizeMb, value); }
|
||||
|
||||
// ── Timeout ──────────────────────────────────────────────────────────────
|
||||
|
||||
private int _apiTimeoutSeconds = 120;
|
||||
public int ApiTimeoutSeconds { get => _apiTimeoutSeconds; set => SetProperty(ref _apiTimeoutSeconds, value); }
|
||||
|
||||
private double _timeoutPerTokenRatio = 0.5;
|
||||
public double TimeoutPerTokenRatio { get => _timeoutPerTokenRatio; set => SetProperty(ref _timeoutPerTokenRatio, value); }
|
||||
|
||||
// ── Verifica qualita ─────────────────────────────────────────────────────
|
||||
|
||||
private bool _enableQualityVerification = false;
|
||||
public bool EnableQualityVerification { get => _enableQualityVerification; set => SetProperty(ref _enableQualityVerification, value); }
|
||||
|
||||
private bool _useSameModelForVerification = true;
|
||||
public bool UseSameModelForVerification { get => _useSameModelForVerification; set => SetProperty(ref _useSameModelForVerification, value); }
|
||||
|
||||
private string _verificationApiEndpoint = "";
|
||||
public string VerificationApiEndpoint { get => _verificationApiEndpoint; set => SetProperty(ref _verificationApiEndpoint, value); }
|
||||
|
||||
private string _verificationModelName = "";
|
||||
public string VerificationModelName { get => _verificationModelName; set => SetProperty(ref _verificationModelName, value); }
|
||||
|
||||
private string _verificationApiKey = "";
|
||||
public string VerificationApiKey { get => _verificationApiKey; set => SetProperty(ref _verificationApiKey, value); }
|
||||
|
||||
private double _revenuePerHighQualityRecord = 0.005;
|
||||
public double RevenuePerHighQualityRecord { get => _revenuePerHighQualityRecord; set => SetProperty(ref _revenuePerHighQualityRecord, value); }
|
||||
|
||||
// ── Costi ────────────────────────────────────────────────────────────────
|
||||
|
||||
private double _electricityCostPerKwh = 0.25;
|
||||
public double ElectricityCostPerKwh { get => _electricityCostPerKwh; set => SetProperty(ref _electricityCostPerKwh, value); }
|
||||
|
||||
private double _systemPowerWatt = 350;
|
||||
public double SystemPowerWatt { get => _systemPowerWatt; set => SetProperty(ref _systemPowerWatt, value); }
|
||||
|
||||
private string _apiCostType = "Free";
|
||||
public string ApiCostType { get => _apiCostType; set => SetProperty(ref _apiCostType, value); }
|
||||
public string[] ApiCostTypes { get; } = { "Free", "PerCall", "PerBlock" };
|
||||
|
||||
private double _apiCostPerCall = 0;
|
||||
public double ApiCostPerCall { get => _apiCostPerCall; set => SetProperty(ref _apiCostPerCall, value); }
|
||||
|
||||
private double _apiCostPerBlock = 0;
|
||||
public double ApiCostPerBlock { get => _apiCostPerBlock; set => SetProperty(ref _apiCostPerBlock, value); }
|
||||
|
||||
private int _apiBlockSize = 1000;
|
||||
public int ApiBlockSize { get => _apiBlockSize; set => SetProperty(ref _apiBlockSize, value); }
|
||||
|
||||
// ── Status ───────────────────────────────────────────────────────────────
|
||||
|
||||
private string _statusMessage = "";
|
||||
public string StatusMessage { get => _statusMessage; set => SetProperty(ref _statusMessage, value); }
|
||||
|
||||
// ── Commands ─────────────────────────────────────────────────────────────
|
||||
|
||||
public RelayCommand SaveCommand { get; }
|
||||
public RelayCommand BrowseOutputDirectoryCommand { get; }
|
||||
public RelayCommand ResetTelemetryCommand { get; }
|
||||
public RelayCommand ResetDatabaseCommand { get; }
|
||||
public RelayCommand InsertDefaultBettingPromptCommand { get; }
|
||||
|
||||
// ── Constructor ──────────────────────────────────────────────────────────
|
||||
|
||||
public SettingsViewModel()
|
||||
{
|
||||
SaveCommand = new RelayCommand(Save);
|
||||
BrowseOutputDirectoryCommand = new RelayCommand(BrowseOutputDirectory);
|
||||
ResetTelemetryCommand = new RelayCommand(ResetTelemetry);
|
||||
ResetDatabaseCommand = new RelayCommand(ResetDatabase);
|
||||
InsertDefaultBettingPromptCommand = new RelayCommand(InsertDefaultBettingPrompt);
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
// ── Private methods ──────────────────────────────────────────────────────
|
||||
|
||||
public void Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = SynthDataDbContext.LoadSettings();
|
||||
ApiEndpoint = s.ApiEndpoint;
|
||||
ModelName = s.ModelName;
|
||||
ApiKey = s.ApiKey;
|
||||
Temperature = s.Temperature;
|
||||
MaxTokens = s.MaxTokens;
|
||||
SystemPrompt = s.SystemPrompt;
|
||||
UserPrompt = s.UserPrompt;
|
||||
OutputDirectory = s.OutputDirectory;
|
||||
OutputFilePrefix = s.OutputFilePrefix;
|
||||
MaxFileSizeMb = s.MaxFileSizeMb;
|
||||
ApiTimeoutSeconds = s.ApiTimeoutSeconds;
|
||||
TimeoutPerTokenRatio = s.TimeoutPerTokenRatio;
|
||||
EnableQualityVerification = s.EnableQualityVerification;
|
||||
UseSameModelForVerification = s.UseSameModelForVerification;
|
||||
VerificationApiEndpoint = s.VerificationApiEndpoint;
|
||||
VerificationModelName = s.VerificationModelName;
|
||||
VerificationApiKey = s.VerificationApiKey;
|
||||
RevenuePerHighQualityRecord = s.RevenuePerHighQualityRecord;
|
||||
ElectricityCostPerKwh = s.ElectricityCostPerKwh;
|
||||
SystemPowerWatt = s.SystemPowerWatt;
|
||||
ApiCostType = s.ApiCostType;
|
||||
ApiCostPerCall = s.ApiCostPerCall;
|
||||
ApiCostPerBlock = s.ApiCostPerBlock;
|
||||
ApiBlockSize = s.ApiBlockSize;
|
||||
|
||||
StatusMessage = "Impostazioni caricate.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = "Errore caricamento: " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(OutputDirectory))
|
||||
{
|
||||
StatusMessage = "Seleziona una cartella di output.";
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
SynthDataDbContext.SaveSettings(new AppSettings
|
||||
{
|
||||
ApiEndpoint = ApiEndpoint,
|
||||
ModelName = ModelName,
|
||||
ApiKey = ApiKey,
|
||||
Temperature = Temperature,
|
||||
MaxTokens = MaxTokens,
|
||||
SystemPrompt = SystemPrompt,
|
||||
UserPrompt = UserPrompt,
|
||||
OutputDirectory = OutputDirectory,
|
||||
OutputFilePrefix = OutputFilePrefix,
|
||||
MaxFileSizeMb = MaxFileSizeMb,
|
||||
ApiTimeoutSeconds = ApiTimeoutSeconds,
|
||||
TimeoutPerTokenRatio = TimeoutPerTokenRatio,
|
||||
EnableQualityVerification = EnableQualityVerification,
|
||||
UseSameModelForVerification = UseSameModelForVerification,
|
||||
VerificationApiEndpoint = VerificationApiEndpoint,
|
||||
VerificationModelName = VerificationModelName,
|
||||
VerificationApiKey = VerificationApiKey,
|
||||
RevenuePerHighQualityRecord = RevenuePerHighQualityRecord,
|
||||
ElectricityCostPerKwh = ElectricityCostPerKwh,
|
||||
SystemPowerWatt = SystemPowerWatt,
|
||||
ApiCostType = ApiCostType,
|
||||
ApiCostPerCall = ApiCostPerCall,
|
||||
ApiCostPerBlock = ApiCostPerBlock,
|
||||
ApiBlockSize = ApiBlockSize,
|
||||
});
|
||||
StatusMessage = "Impostazioni salvate.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = "Errore salvataggio: " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void BrowseOutputDirectory()
|
||||
{
|
||||
var dlg = new System.Windows.Forms.FolderBrowserDialog
|
||||
{
|
||||
Description = "Seleziona la cartella di output",
|
||||
SelectedPath = OutputDirectory
|
||||
};
|
||||
if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
|
||||
OutputDirectory = dlg.SelectedPath;
|
||||
}
|
||||
|
||||
private void ResetTelemetry()
|
||||
{
|
||||
if (MessageBox.Show("Eliminare TUTTA la telemetria?", "Conferma", MessageBoxButton.YesNo, MessageBoxImage.Warning) != MessageBoxResult.Yes)
|
||||
return;
|
||||
try { SynthDataDbContext.DeleteAllTelemetry(); StatusMessage = "Telemetria eliminata."; }
|
||||
catch (Exception ex) { StatusMessage = "Errore: " + ex.Message; }
|
||||
}
|
||||
|
||||
private void ResetDatabase()
|
||||
{
|
||||
if (MessageBox.Show("Resettare COMPLETAMENTE il database? Tutte le impostazioni e la telemetria verranno cancellate.",
|
||||
"Conferma Reset", MessageBoxButton.YesNo, MessageBoxImage.Stop) != MessageBoxResult.Yes)
|
||||
return;
|
||||
try { SynthDataDbContext.ResetDatabase(); Load(); StatusMessage = "Database resettato."; }
|
||||
catch (Exception ex) { StatusMessage = "Errore: " + ex.Message; }
|
||||
}
|
||||
|
||||
private void InsertDefaultBettingPrompt()
|
||||
{
|
||||
SystemPrompt =
|
||||
"Sei il nodo validatore di una blockchain per una piattaforma di scommesse decentralizzata. " +
|
||||
"Il tuo compito e generare transazioni fittizie ma realistiche in formato JSON puro. " +
|
||||
"Non includere testo introduttivo o conclusivo, restituisci solo un array di oggetti JSON.\n\n" +
|
||||
"Regole per la generazione dei dati:\n" +
|
||||
"- Ogni transazione deve avere un tx_hash esadecimale casuale di 64 caratteri e un timestamp ISO 8601.\n" +
|
||||
"- bet_type puo essere solo 'singola' o 'multipla'.\n" +
|
||||
"- Genera un bankroll_at_time casuale tra 100 e 5000.\n" +
|
||||
"- Logica dello Stake: Se 'singola', stake_amount deve essere intero e multiplo di 5. " +
|
||||
"Se 'multipla', genera confidence_level tra 0.0 e 1.0. Se confidenza tra 0.9 e 1.0, " +
|
||||
"stake_amount fino al 10% del bankroll. Altrimenti sotto l'1%.\n" +
|
||||
"- Includi smart_contract_log che spiega come lo smart contract ha processato i fondi.";
|
||||
|
||||
UserPrompt = "Genera un blocco di 5 nuove transazioni sequenziali rispettando rigorosamente le regole di business.";
|
||||
|
||||
StatusMessage = "Prompt blockchain betting inseriti. Ricorda di salvare.";
|
||||
}
|
||||
|
||||
private void ApplyPreset(string preset)
|
||||
{
|
||||
switch (preset)
|
||||
{
|
||||
case "OpenAI":
|
||||
ApiEndpoint = "https://api.openai.com/v1/chat/completions";
|
||||
ModelName = "gpt-4o";
|
||||
StatusMessage = "Preset OpenAI applicato. Inserisci la API Key.";
|
||||
break;
|
||||
case "Anthropic":
|
||||
ApiEndpoint = "https://api.anthropic.com/v1/messages";
|
||||
ModelName = "claude-3-5-sonnet-20241022";
|
||||
StatusMessage = "Preset Anthropic applicato. Inserisci la API Key.";
|
||||
break;
|
||||
case "Google AI":
|
||||
ApiEndpoint = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions";
|
||||
ModelName = "gemini-2.0-flash-exp";
|
||||
StatusMessage = "Preset Google AI applicato. Inserisci la API Key.";
|
||||
break;
|
||||
case "Azure OpenAI":
|
||||
ApiEndpoint = "https://<resource>.openai.azure.com/openai/deployments/<deployment>/chat/completions?api-version=2024-02-15-preview";
|
||||
ModelName = "gpt-4";
|
||||
StatusMessage = "Sostituisci <resource> e <deployment>.";
|
||||
break;
|
||||
case "LM Studio (Local)":
|
||||
ApiEndpoint = "http://127.0.0.1:1234/v1/chat/completions";
|
||||
ModelName = "";
|
||||
ApiKey = "";
|
||||
StatusMessage = "Preset LM Studio applicato.";
|
||||
break;
|
||||
case "Ollama (Local)":
|
||||
ApiEndpoint = "http://127.0.0.1:11434/v1/chat/completions";
|
||||
ModelName = "llama3.3";
|
||||
ApiKey = "";
|
||||
StatusMessage = "Preset Ollama applicato.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Dione.Data;
|
||||
using Dione.Models;
|
||||
|
||||
namespace Dione.ViewModels
|
||||
{
|
||||
public class TelemetryHistoryViewModel : ObservableObject
|
||||
{
|
||||
private ObservableCollection<TelemetryLog> _logs = new ObservableCollection<TelemetryLog>();
|
||||
public ObservableCollection<TelemetryLog> Logs { get => _logs; set => SetProperty(ref _logs, value); }
|
||||
|
||||
// Filters
|
||||
private string _filterBatchId = "";
|
||||
public string FilterBatchId { get => _filterBatchId; set => SetProperty(ref _filterBatchId, value); }
|
||||
|
||||
private string _filterModel = "";
|
||||
public string FilterModel { get => _filterModel; set => SetProperty(ref _filterModel, value); }
|
||||
|
||||
private bool? _filterSuccess;
|
||||
public bool? FilterSuccess { get => _filterSuccess; set => SetProperty(ref _filterSuccess, value); }
|
||||
|
||||
public string[] SuccessOptions { get; } = new[] { "All", "Success", "Errors" };
|
||||
|
||||
private string _selectedSuccessOption = "All";
|
||||
public string SelectedSuccessOption
|
||||
{
|
||||
get => _selectedSuccessOption;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedSuccessOption, value))
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case "Success": FilterSuccess = true; break;
|
||||
case "Errors": FilterSuccess = false; break;
|
||||
default: FilterSuccess = null; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats
|
||||
private int _totalCount;
|
||||
public int TotalCount { get => _totalCount; set => SetProperty(ref _totalCount, value); }
|
||||
|
||||
private int _filteredCount;
|
||||
public int FilteredCount { get => _filteredCount; set => SetProperty(ref _filteredCount, value); }
|
||||
|
||||
private string _statusMessage = "";
|
||||
public string StatusMessage { get => _statusMessage; set => SetProperty(ref _statusMessage, value); }
|
||||
|
||||
public RelayCommand RefreshCommand { get; }
|
||||
public RelayCommand ApplyFilterCommand { get; }
|
||||
public RelayCommand ClearFilterCommand { get; }
|
||||
|
||||
public TelemetryHistoryViewModel()
|
||||
{
|
||||
RefreshCommand = new RelayCommand(Refresh);
|
||||
ApplyFilterCommand = new RelayCommand(ApplyFilter);
|
||||
ClearFilterCommand = new RelayCommand(ClearFilter);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
try
|
||||
{
|
||||
var all = SynthDataDbContext.QueryAll();
|
||||
TotalCount = all.Count;
|
||||
ApplyFilterToList(all);
|
||||
StatusMessage = $"Loaded {TotalCount} total records from database.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = "Error: " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFilter()
|
||||
{
|
||||
try
|
||||
{
|
||||
var all = SynthDataDbContext.QueryAll();
|
||||
TotalCount = all.Count;
|
||||
ApplyFilterToList(all);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = "Error: " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFilterToList(System.Collections.Generic.List<TelemetryLog> all)
|
||||
{
|
||||
var filtered = all.AsEnumerable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(FilterBatchId))
|
||||
filtered = filtered.Where(l => (l.BatchId ?? "").IndexOf(FilterBatchId, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(FilterModel))
|
||||
filtered = filtered.Where(l => (l.ModelUsed ?? "").IndexOf(FilterModel, StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
|
||||
if (FilterSuccess.HasValue)
|
||||
filtered = filtered.Where(l => l.IsSuccess == FilterSuccess.Value);
|
||||
|
||||
var result = filtered.OrderByDescending(l => l.Timestamp).ToList();
|
||||
FilteredCount = result.Count;
|
||||
Logs = new ObservableCollection<TelemetryLog>(result);
|
||||
StatusMessage = $"Showing {FilteredCount} of {TotalCount} records.";
|
||||
}
|
||||
|
||||
private void ClearFilter()
|
||||
{
|
||||
FilterBatchId = "";
|
||||
FilterModel = "";
|
||||
SelectedSuccessOption = "All";
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<UserControl x:Class="Dione.Views.DashboardView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
|
||||
xmlns:vm="clr-namespace:Dione.ViewModels"
|
||||
Background="Transparent">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="StatCard" TargetType="Border">
|
||||
<Setter Property="Background" Value="#1E1E2E"/>
|
||||
<Setter Property="CornerRadius" Value="12"/>
|
||||
<Setter Property="Padding" Value="20,16"/>
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
</Style>
|
||||
<Style x:Key="StatValue" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="28"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Style>
|
||||
<Style x:Key="StatLabel" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Foreground" Value="#90CAF9"/>
|
||||
<Setter Property="Margin" Value="0,4,0,0"/>
|
||||
</Style>
|
||||
<Style x:Key="ChartCard" TargetType="Border">
|
||||
<Setter Property="Background" Value="#1E1E2E"/>
|
||||
<Setter Property="CornerRadius" Value="12"/>
|
||||
<Setter Property="Padding" Value="16"/>
|
||||
<Setter Property="Margin" Value="8"/>
|
||||
</Style>
|
||||
<Style x:Key="ChartTitle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Foreground" Value="#B0BEC5"/>
|
||||
<Setter Property="Margin" Value="0,0,0,8"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="16">
|
||||
<DockPanel Margin="8,0,0,16">
|
||||
<TextBlock Text="Dashboard" FontSize="26" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
|
||||
<Button Content="Refresh" Command="{Binding RefreshCommand}" DockPanel.Dock="Right"
|
||||
HorizontalAlignment="Right" Padding="16,6" FontSize="12"
|
||||
Background="#2A2A3E" Foreground="#90CAF9" BorderThickness="0" Cursor="Hand">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="6" Padding="{TemplateBinding Padding}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<ContentPresenter VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Empty state guidance -->
|
||||
<Border Background="#1A1A26" CornerRadius="10" Padding="24" Margin="8,0,8,16">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ValidRecords}" Value="0">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="36" Foreground="#37474F" HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="Nessun dato disponibile" FontSize="18" FontWeight="SemiBold" Foreground="#78909C" HorizontalAlignment="Center" Margin="0,10,0,6"/>
|
||||
<TextBlock TextWrapping="Wrap" TextAlignment="Center" Foreground="#546E7A" FontSize="13" MaxWidth="500">
|
||||
Per iniziare: vai in Settings per configurare un profilo, poi in Data Designer per generare il prompt, e infine in Live Generation per avviare la generazione dati. I grafici si popoleranno automaticamente.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- KPI Cards -->
|
||||
<UniformGrid Rows="1" Columns="4">
|
||||
<Border Style="{StaticResource StatCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="TOKENS" FontSize="11" Foreground="#546E7A" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding TotalTokensSpent, StringFormat=N0}" Style="{StaticResource StatValue}"/>
|
||||
<TextBlock Text="Total Tokens Spent" Style="{StaticResource StatLabel}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource StatCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="RECORDS" FontSize="11" Foreground="#546E7A" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding ValidRecords, StringFormat=N0}" Style="{StaticResource StatValue}"/>
|
||||
<TextBlock Text="Valid Records" Style="{StaticResource StatLabel}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource StatCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="COST" FontSize="11" Foreground="#546E7A" FontWeight="Bold"/>
|
||||
<TextBlock Style="{StaticResource StatValue}">
|
||||
<TextBlock.Text>
|
||||
<Binding Path="EstimatedCostEur" StringFormat="EUR {0:F2}"/>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Text="Est. Electric Cost" Style="{StaticResource StatLabel}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Style="{StaticResource StatCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="QUALITY" FontSize="11" Foreground="#546E7A" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding QualityScore, StringFormat={}{0:F1}%}" Style="{StaticResource StatValue}"/>
|
||||
<TextBlock Text="Quality Score" Style="{StaticResource StatLabel}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UniformGrid>
|
||||
|
||||
<!-- Charts Row 1 -->
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Style="{StaticResource ChartCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Tokens Generated / Minute" Style="{StaticResource ChartTitle}"/>
|
||||
<lvc:CartesianChart Height="220" Series="{Binding TokensPerMinuteSeries}" LegendLocation="None">
|
||||
<lvc:CartesianChart.AxisX>
|
||||
<lvc:Axis Labels="{Binding TokensPerMinuteLabels}" Foreground="#78909C"/>
|
||||
</lvc:CartesianChart.AxisX>
|
||||
<lvc:CartesianChart.AxisY>
|
||||
<lvc:Axis Foreground="#78909C"/>
|
||||
</lvc:CartesianChart.AxisY>
|
||||
</lvc:CartesianChart>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="1" Style="{StaticResource ChartCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Success vs Errors" Style="{StaticResource ChartTitle}"/>
|
||||
<lvc:PieChart Height="220" Series="{Binding SuccessErrorSeries}" LegendLocation="Bottom" InnerRadius="50"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Latency bar chart -->
|
||||
<Border Style="{StaticResource ChartCard}" Margin="8,8,8,8">
|
||||
<StackPanel>
|
||||
<TextBlock Text="API Latency (ms)" Style="{StaticResource ChartTitle}"/>
|
||||
<lvc:CartesianChart Height="200" Series="{Binding LatencySeries}" LegendLocation="None">
|
||||
<lvc:CartesianChart.AxisX>
|
||||
<lvc:Axis Labels="{Binding LatencyLabels}" Foreground="#78909C"/>
|
||||
</lvc:CartesianChart.AxisX>
|
||||
<lvc:CartesianChart.AxisY>
|
||||
<lvc:Axis Foreground="#78909C" Title="ms"/>
|
||||
</lvc:CartesianChart.AxisY>
|
||||
</lvc:CartesianChart>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Dione.Views
|
||||
{
|
||||
public partial class DashboardView : UserControl
|
||||
{
|
||||
public DashboardView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<UserControl x:Class="Dione.Views.DataDesignerView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Background="Transparent">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="FieldLabel" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="#90CAF9"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Margin" Value="0,0,0,4"/>
|
||||
</Style>
|
||||
<Style x:Key="EditorBox" TargetType="TextBox">
|
||||
<Setter Property="Background" Value="#1E1E2E"/>
|
||||
<Setter Property="Foreground" Value="#E0E0E0"/>
|
||||
<Setter Property="BorderBrush" Value="#2A2A3E"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="10,8"/>
|
||||
<Setter Property="FontFamily" Value="Consolas"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="AcceptsReturn" Value="True"/>
|
||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
|
||||
<Setter Property="CaretBrush" Value="White"/>
|
||||
</Style>
|
||||
<Style x:Key="ActionBtn" TargetType="Button">
|
||||
<Setter Property="Background" Value="#2962FF"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Padding" Value="18,8"/>
|
||||
<Setter Property="Margin" Value="0,0,8,0"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="6" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="SecondaryBtn" BasedOn="{StaticResource ActionBtn}" TargetType="Button">
|
||||
<Setter Property="Background" Value="#2A2A3E"/>
|
||||
<Setter Property="Foreground" Value="#90CAF9"/>
|
||||
</Style>
|
||||
<Style x:Key="SectionCard" TargetType="Border">
|
||||
<Setter Property="Background" Value="#1A1A26"/>
|
||||
<Setter Property="CornerRadius" Value="10"/>
|
||||
<Setter Property="Padding" Value="20"/>
|
||||
<Setter Property="Margin" Value="0,0,0,16"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="24,16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title + profile selector -->
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,16">
|
||||
<TextBlock Text="Data Designer" FontSize="26" FontWeight="Bold" Foreground="White" Margin="0,0,0,12"/>
|
||||
<DockPanel>
|
||||
<Button Content="Auto-Generate" Command="{Binding GenerateTemplateCommand}" Style="{StaticResource ActionBtn}" DockPanel.Dock="Right"/>
|
||||
<Button Content="Refresh" Command="{Binding RefreshCommand}" Style="{StaticResource SecondaryBtn}" DockPanel.Dock="Right" Margin="0,0,8,0"/>
|
||||
<ComboBox ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}"
|
||||
DisplayMemberPath="Name"
|
||||
Background="#1E1E2E" Foreground="White" BorderBrush="#2A2A3E" Padding="8,6" Margin="0,0,8,0"/>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Schema preview -->
|
||||
<Border Grid.Row="1" Style="{StaticResource SectionCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="SCHEMA (from Settings)" FontSize="11" FontWeight="Bold" Foreground="#546E7A" Margin="0,0,0,6"/>
|
||||
<TextBox Text="{Binding SchemaPreview, Mode=OneWay}" Style="{StaticResource EditorBox}" Height="60" IsReadOnly="True" Opacity="0.7"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Editors in two columns -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- System Prompt -->
|
||||
<Border Grid.Column="0" Style="{StaticResource SectionCard}">
|
||||
<DockPanel>
|
||||
<TextBlock Text="SYSTEM PROMPT" FontSize="11" FontWeight="Bold" Foreground="#546E7A" Margin="0,0,0,8" DockPanel.Dock="Top"/>
|
||||
<TextBox Text="{Binding SystemPrompt, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource EditorBox}"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Few-Shot Examples -->
|
||||
<Border Grid.Column="2" Style="{StaticResource SectionCard}">
|
||||
<DockPanel>
|
||||
<TextBlock Text="FEW-SHOT EXAMPLES" FontSize="11" FontWeight="Bold" Foreground="#546E7A" Margin="0,0,0,8" DockPanel.Dock="Top"/>
|
||||
<TextBox Text="{Binding FewShotExamples, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource EditorBox}"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Bottom: Save + status -->
|
||||
<DockPanel Grid.Row="3" Margin="0,8,0,0">
|
||||
<Button Content="Save Prompt to Profile" Command="{Binding SavePromptCommand}" Style="{StaticResource ActionBtn}" DockPanel.Dock="Left"/>
|
||||
<TextBlock Text="{Binding StatusMessage}" Foreground="#4CAF50" FontSize="12" VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Dione.Views
|
||||
{
|
||||
public partial class DataDesignerView : UserControl
|
||||
{
|
||||
public DataDesignerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
<UserControl x:Class="Dione.Views.LiveGenerationView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Background="Transparent">
|
||||
|
||||
<UserControl.Resources>
|
||||
|
||||
<!-- Colori di accento -->
|
||||
<SolidColorBrush x:Key="AccentGreen" Color="#4CAF50"/>
|
||||
<SolidColorBrush x:Key="AccentRed" Color="#EF5350"/>
|
||||
<SolidColorBrush x:Key="AccentBlue" Color="#42A5F5"/>
|
||||
<SolidColorBrush x:Key="AccentPurple" Color="#AB47BC"/>
|
||||
<SolidColorBrush x:Key="AccentOrange" Color="#FFA726"/>
|
||||
<SolidColorBrush x:Key="AccentTeal" Color="#26C6DA"/>
|
||||
<SolidColorBrush x:Key="Muted" Color="#546E7A"/>
|
||||
|
||||
<!-- Card generica -->
|
||||
<Style x:Key="Card" TargetType="Border">
|
||||
<Setter Property="Background" Value="#1A1A2E"/>
|
||||
<Setter Property="CornerRadius" Value="14"/>
|
||||
<Setter Property="Padding" Value="20,16"/>
|
||||
<Setter Property="Margin" Value="0,0,0,12"/>
|
||||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect BlurRadius="18" ShadowDepth="2" Opacity="0.25" Color="#000000"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- KPI pill card -->
|
||||
<Style x:Key="KpiCard" TargetType="Border">
|
||||
<Setter Property="Background" Value="#1E1E32"/>
|
||||
<Setter Property="CornerRadius" Value="12"/>
|
||||
<Setter Property="Padding" Value="16,12"/>
|
||||
<Setter Property="Margin" Value="0,0,10,10"/>
|
||||
<Setter Property="MinWidth" Value="110"/>
|
||||
</Style>
|
||||
|
||||
<!-- Bottone principale arrotondato -->
|
||||
<Style x:Key="RoundBtn" TargetType="Button">
|
||||
<Setter Property="Padding" Value="20,10"/>
|
||||
<Setter Property="Margin" Value="0,0,8,0"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}"
|
||||
CornerRadius="20" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Opacity" Value="0.82"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="Bd" Property="Opacity" Value="0.35"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Bottone icona tondo -->
|
||||
<Style x:Key="IconBtn" TargetType="Button">
|
||||
<Setter Property="Width" Value="36"/>
|
||||
<Setter Property="Height" Value="36"/>
|
||||
<Setter Property="Margin" Value="0,0,6,0"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Foreground" Value="#78909C"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="Bd" Background="#252540" CornerRadius="18">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#2E2E50"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Log list item style -->
|
||||
<Style x:Key="LogItem" TargetType="ListBoxItem">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Padding" Value="0,2"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" Background="Transparent" CornerRadius="4" Padding="6,3" Margin="0,1">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#22FFFFFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Separatore verticale leggero -->
|
||||
<Style x:Key="VSep" TargetType="Border">
|
||||
<Setter Property="Width" Value="1"/>
|
||||
<Setter Property="Background" Value="#252540"/>
|
||||
<Setter Property="Margin" Value="16,6"/>
|
||||
</Style>
|
||||
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="20,16,20,12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/> <!-- Header toolbar -->
|
||||
<RowDefinition Height="Auto"/> <!-- KPI strip -->
|
||||
<RowDefinition Height="Auto"/> <!-- File progress bar -->
|
||||
<RowDefinition Height="*"/> <!-- Preview + Log -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
ROW 0 — HEADER TOOLBAR
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<Border Grid.Row="0" Style="{StaticResource Card}" Margin="0,0,0,12">
|
||||
<DockPanel VerticalAlignment="Center">
|
||||
|
||||
<!-- Titolo + stato pill -->
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Text="Generazione" FontSize="20" FontWeight="Bold"
|
||||
Foreground="White" VerticalAlignment="Center"/>
|
||||
<Border CornerRadius="10" Padding="10,4" Margin="14,0,0,0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#1B3A1F"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsRunning}" Value="False">
|
||||
<Setter Property="Background" Value="#252540"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<!-- Pallino animato -->
|
||||
<Ellipse Width="7" Height="7" VerticalAlignment="Center" Margin="0,0,6,0">
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="Ellipse">
|
||||
<Setter Property="Fill" Value="#546E7A"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsRunning}" Value="True">
|
||||
<Setter Property="Fill" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
</Ellipse>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="11" FontWeight="SemiBold">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="Fermo"/>
|
||||
<Setter Property="Foreground" Value="#546E7A"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsRunning}" Value="True">
|
||||
<Setter Property="Text" Value="In esecuzione"/>
|
||||
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Pulsanti a destra -->
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
|
||||
<!-- Timer -->
|
||||
<Border CornerRadius="10" Background="#1E1E32" Padding="12,6" Margin="0,0,14,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="12"
|
||||
Foreground="#546E7A" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock Text="{Binding ElapsedTime}" Foreground="#90CAF9"
|
||||
FontSize="13" FontFamily="Consolas" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Button Command="{Binding StartCommand}" Style="{StaticResource RoundBtn}" Background="#1B5E20">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="13" VerticalAlignment="Center" Margin="0,0,7,0"/>
|
||||
<TextBlock Text="Avvia" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Command="{Binding StopCommand}" Style="{StaticResource RoundBtn}" Background="#B71C1C">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="13" VerticalAlignment="Center" Margin="0,0,7,0"/>
|
||||
<TextBlock Text="Stop" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
ROW 1 — KPI STRIP
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<UniformGrid Grid.Row="1" Rows="1" Margin="0,0,0,12">
|
||||
|
||||
<!-- Batch OK -->
|
||||
<Border Style="{StaticResource KpiCard}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<Border Width="28" Height="28" CornerRadius="14" Background="#1B3A1F">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||
Foreground="#4CAF50" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="BATCH OK" Foreground="#546E7A" FontSize="10"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding SuccessCount}" Foreground="#4CAF50"
|
||||
FontSize="26" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Errori -->
|
||||
<Border Style="{StaticResource KpiCard}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<Border Width="28" Height="28" CornerRadius="14" Background="#3E1A1A">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||
Foreground="#EF5350" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="ERRORI" Foreground="#546E7A" FontSize="10"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding ErrorCount}" Foreground="#EF5350"
|
||||
FontSize="26" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Record scritti -->
|
||||
<Border Style="{StaticResource KpiCard}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<Border Width="28" Height="28" CornerRadius="14" Background="#0D2A3E">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||
Foreground="#42A5F5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="RECORD" Foreground="#546E7A" FontSize="10"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding TotalRecordsWritten}" Foreground="#42A5F5"
|
||||
FontSize="26" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Token totali -->
|
||||
<Border Style="{StaticResource KpiCard}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<Border Width="28" Height="28" CornerRadius="14" Background="#2A1A3E">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||
Foreground="#AB47BC" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="TOKEN" Foreground="#546E7A" FontSize="10"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding TokensTotal, StringFormat={}{0:N0}}" Foreground="#AB47BC"
|
||||
FontSize="26" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- File creati -->
|
||||
<Border Style="{StaticResource KpiCard}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<Border Width="28" Height="28" CornerRadius="14" Background="#0D2E2E">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||
Foreground="#26C6DA" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="FILE" Foreground="#546E7A" FontSize="10"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding FilesCreated}" Foreground="#26C6DA"
|
||||
FontSize="26" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Tempo medio batch -->
|
||||
<Border Style="{StaticResource KpiCard}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<Border Width="28" Height="28" CornerRadius="14" Background="#2E2A0D">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||
Foreground="#FFA726" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<TextBlock Text="AVG BATCH" Foreground="#546E7A" FontSize="10"
|
||||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding AvgBatchTimeMs, StringFormat={}{0:F0} ms}"
|
||||
Foreground="#FFA726" FontSize="22" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</UniformGrid>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
ROW 2 — FILE PROGRESS
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<Border Grid.Row="2" Style="{StaticResource Card}" Padding="20,14" Margin="0,0,0,12">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="8"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- File name -->
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||
Foreground="#26C6DA" VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<TextBlock Text="{Binding CurrentFileName, TargetNullValue='—'}"
|
||||
Foreground="White" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Dimensione a destra -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="2"
|
||||
Foreground="#546E7A" FontSize="11" VerticalAlignment="Center">
|
||||
<Run Text="{Binding CurrentFileSizeMb, StringFormat={}{0:F1}, Mode=OneWay}"/>
|
||||
<Run Text=" / "/>
|
||||
<Run Text="{Binding DataContext.MaxFileSizeMb, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat={}{0} MB, FallbackValue='250 MB'}"/>
|
||||
</TextBlock>
|
||||
|
||||
<!-- Progress bar arrotondata -->
|
||||
<Border Grid.Row="2" Grid.ColumnSpan="3" Height="8" CornerRadius="4" Background="#252540" Margin="0,6,0,0">
|
||||
<Border CornerRadius="4" HorizontalAlignment="Left" Background="#26C6DA">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="MinWidth" Value="0"/>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<!-- Width proporzionale tramite converter non disponibile: usiamo ProgressBar nascosta -->
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<!-- ProgressBar trasparente che calcola la percentuale -->
|
||||
<ProgressBar Grid.Row="2" Grid.ColumnSpan="3"
|
||||
Height="8" Margin="0,6,0,0"
|
||||
Minimum="0" Maximum="{Binding MaxFileSizeMbDouble, FallbackValue=250}"
|
||||
Value="{Binding CurrentFileSizeMb, Mode=OneWay}"
|
||||
Foreground="#26C6DA" Background="#252540">
|
||||
<ProgressBar.Template>
|
||||
<ControlTemplate TargetType="ProgressBar">
|
||||
<Border CornerRadius="4" Background="{TemplateBinding Background}">
|
||||
<Border x:Name="PART_Track" CornerRadius="4" Background="{TemplateBinding Background}">
|
||||
<Border x:Name="PART_Indicator" HorizontalAlignment="Left"
|
||||
CornerRadius="4" Background="{TemplateBinding Foreground}"/>
|
||||
</Border>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</ProgressBar.Template>
|
||||
</ProgressBar>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════
|
||||
ROW 3 — PREVIEW + LOG
|
||||
══════════════════════════════════════════════════════════════ -->
|
||||
<Grid Grid.Row="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="5*"/>
|
||||
<ColumnDefinition Width="12"/>
|
||||
<ColumnDefinition Width="7*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Preview / Streaming -->
|
||||
<Border Grid.Column="0" Style="{StaticResource Card}" Padding="16,14">
|
||||
<DockPanel>
|
||||
|
||||
<!-- Header con badge dinamico -->
|
||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="12"
|
||||
Foreground="#26C6DA" VerticalAlignment="Center" Margin="0,0,7,0"/>
|
||||
<TextBlock Foreground="#546E7A" FontSize="10" FontWeight="Bold"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="ULTIMO OUTPUT"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsStreaming}" Value="True">
|
||||
<Setter Property="Text" Value="STREAMING IN CORSO"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Badge LIVE / JSONL -->
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
|
||||
|
||||
<!-- Contatori streaming (visibili solo durante streaming) -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,8,0">
|
||||
<StackPanel.Style>
|
||||
<Style TargetType="StackPanel">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsStreaming}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</StackPanel.Style>
|
||||
<Border CornerRadius="4" Background="#1A2A4A" Padding="6,2" Margin="0,0,5,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="9" Foreground="#AB47BC"
|
||||
VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="{Binding StreamingTokensLive, StringFormat={}{0} tok}"
|
||||
Foreground="#AB47BC" FontFamily="Consolas" FontSize="10"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border CornerRadius="4" Background="#0D2E2E" Padding="6,2">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="9" Foreground="#26C6DA"
|
||||
VerticalAlignment="Center" Margin="0,0,4,0"/>
|
||||
<TextBlock Text="{Binding StreamingChars, StringFormat={}{0} car}"
|
||||
Foreground="#26C6DA" FontFamily="Consolas" FontSize="10"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Badge LIVE (animato) -->
|
||||
<Border CornerRadius="4" Padding="6,2">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#0D2E2E"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsStreaming}" Value="True">
|
||||
<Setter Property="Background" Value="#1B3A1F"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Ellipse Width="6" Height="6" VerticalAlignment="Center" Margin="0,0,5,0">
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="Ellipse">
|
||||
<Setter Property="Fill" Value="#26C6DA"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsStreaming}" Value="True">
|
||||
<Setter Property="Fill" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
</Ellipse>
|
||||
<TextBlock FontSize="9" FontWeight="Bold">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="JSONL"/>
|
||||
<Setter Property="Foreground" Value="#26C6DA"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsStreaming}" Value="True">
|
||||
<Setter Property="Text" Value="LIVE"/>
|
||||
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Cursore lampeggiante durante lo streaming -->
|
||||
<Border DockPanel.Dock="Bottom" Height="2" CornerRadius="1" Margin="0,6,0,0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsStreaming}" Value="True">
|
||||
<Setter Property="Background" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
|
||||
<!-- Testo: StreamingText durante streaming, LastPreview a riposo -->
|
||||
<ScrollViewer x:Name="StreamScrollViewer" VerticalScrollBarVisibility="Auto">
|
||||
<TextBox IsReadOnly="True" TextWrapping="Wrap"
|
||||
Background="Transparent"
|
||||
BorderThickness="0" FontFamily="Consolas" FontSize="11"
|
||||
VerticalContentAlignment="Top" Padding="0">
|
||||
<TextBox.Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Text" Value="{Binding LastPreview, Mode=OneWay}"/>
|
||||
<Setter Property="Foreground" Value="#80CBC4"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsStreaming}" Value="True">
|
||||
<Setter Property="Text" Value="{Binding StreamingText, Mode=OneWay}"/>
|
||||
<Setter Property="Foreground" Value="#C8E6C9"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBox.Style>
|
||||
</TextBox>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Log -->
|
||||
<Border Grid.Column="2" Style="{StaticResource Card}" Padding="16,14">
|
||||
<DockPanel>
|
||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="12"
|
||||
Foreground="#FFA726" VerticalAlignment="Center" Margin="0,0,7,0"/>
|
||||
<TextBlock Text="LOG IN TEMPO REALE" Foreground="#546E7A"
|
||||
FontSize="10" FontWeight="Bold" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
|
||||
<!-- Copia log -->
|
||||
<Button Command="{Binding CopyLogsCommand}" Style="{StaticResource IconBtn}"
|
||||
ToolTip="Copia tutti i log negli appunti">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"/>
|
||||
</Button>
|
||||
<!-- Pulisci log -->
|
||||
<Button Command="{Binding ClearLogCommand}" Style="{StaticResource IconBtn}"
|
||||
ToolTip="Pulisci log">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<ListBox ItemsSource="{Binding LogEntries}"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
ItemContainerStyle="{StaticResource LogItem}"
|
||||
VirtualizingStackPanel.IsVirtualizing="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Badge livello -->
|
||||
<Border Grid.Column="0" CornerRadius="4" Padding="5,1" Margin="0,0,8,0"
|
||||
VerticalAlignment="Top" MinWidth="38">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#1A2A4A"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Level}" Value="ERR">
|
||||
<Setter Property="Background" Value="#3E1A1A"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Level}" Value="WARN">
|
||||
<Setter Property="Background" Value="#2E200A"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Level}" Value="OK">
|
||||
<Setter Property="Background" Value="#1A3A1A"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<TextBlock Text="{Binding Level}" FontFamily="Consolas"
|
||||
FontSize="9" FontWeight="Bold" TextAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="#42A5F5"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Level}" Value="ERR">
|
||||
<Setter Property="Foreground" Value="#EF5350"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Level}" Value="WARN">
|
||||
<Setter Property="Foreground" Value="#FFA726"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Level}" Value="OK">
|
||||
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
<!-- Messaggio -->
|
||||
<TextBlock Grid.Column="1" Text="{Binding Message}"
|
||||
FontFamily="Consolas" FontSize="11"
|
||||
TextWrapping="Wrap" VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="#B0BEC5"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Level}" Value="ERR">
|
||||
<Setter Property="Foreground" Value="#EF9A9A"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Level}" Value="WARN">
|
||||
<Setter Property="Foreground" Value="#FFCC80"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Level}" Value="OK">
|
||||
<Setter Property="Foreground" Value="#A5D6A7"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<!-- Timestamp -->
|
||||
<TextBlock Grid.Column="2" Text="{Binding TimeLabel}"
|
||||
FontFamily="Consolas" FontSize="9"
|
||||
Foreground="#37474F" VerticalAlignment="Center"
|
||||
Margin="8,0,0,0"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Controls;
|
||||
using Dione.ViewModels;
|
||||
|
||||
namespace Dione.Views
|
||||
{
|
||||
public partial class LiveGenerationView : UserControl
|
||||
{
|
||||
public LiveGenerationView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
}
|
||||
|
||||
private LiveGenerationViewModel _vm;
|
||||
|
||||
private void OnDataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (_vm != null)
|
||||
_vm.PropertyChanged -= OnVmPropertyChanged;
|
||||
|
||||
_vm = e.NewValue as LiveGenerationViewModel;
|
||||
|
||||
if (_vm != null)
|
||||
_vm.PropertyChanged += OnVmPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnVmPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(LiveGenerationViewModel.StreamingText) && _vm.IsStreaming)
|
||||
{
|
||||
var sv = FindName("StreamScrollViewer") as ScrollViewer;
|
||||
sv?.ScrollToBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<UserControl x:Class="Dione.Views.PlaceholderView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Background="Transparent">
|
||||
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<StackPanel>
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="48" HorizontalAlignment="Center" Foreground="#546E7A"/>
|
||||
<TextBlock x:Name="TitleText" Text="Coming Soon" FontSize="24" FontWeight="SemiBold"
|
||||
Foreground="#90CAF9" HorizontalAlignment="Center" Margin="0,12,0,0"/>
|
||||
<TextBlock Text="This module is under development." FontSize="14"
|
||||
Foreground="#546E7A" HorizontalAlignment="Center" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Dione.Views
|
||||
{
|
||||
public partial class PlaceholderView : UserControl
|
||||
{
|
||||
public PlaceholderView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public PlaceholderView(string title) : this()
|
||||
{
|
||||
TitleText.Text = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<UserControl x:Class="Dione.Views.SettingsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:Dione.Converters"
|
||||
Background="Transparent">
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:InverseBoolConverter x:Key="InverseBoolConverter"/>
|
||||
|
||||
<Style x:Key="SectionCard" TargetType="Border">
|
||||
<Setter Property="Background" Value="#1A1A28"/>
|
||||
<Setter Property="CornerRadius" Value="10"/>
|
||||
<Setter Property="Padding" Value="20,16"/>
|
||||
<Setter Property="Margin" Value="0,0,0,14"/>
|
||||
</Style>
|
||||
<Style x:Key="Label" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="#90CAF9"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Margin" Value="0,0,0,4"/>
|
||||
</Style>
|
||||
<Style x:Key="Field" TargetType="TextBox">
|
||||
<Setter Property="Background" Value="#0D0D18"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#2A2A3E"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="Margin" Value="0,0,0,10"/>
|
||||
<Setter Property="CaretBrush" Value="White"/>
|
||||
</Style>
|
||||
<Style x:Key="Combo" TargetType="ComboBox">
|
||||
<Setter Property="Background" Value="#0D0D18"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#2A2A3E"/>
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="Margin" Value="0,0,0,10"/>
|
||||
</Style>
|
||||
<Style x:Key="Btn" TargetType="Button">
|
||||
<Setter Property="Background" Value="#2962FF"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Padding" Value="16,7"/>
|
||||
<Setter Property="Margin" Value="0,0,8,0"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="6" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="DangerBtn" BasedOn="{StaticResource Btn}" TargetType="Button">
|
||||
<Setter Property="Background" Value="#C62828"/>
|
||||
</Style>
|
||||
<Style x:Key="GreenBtn" BasedOn="{StaticResource Btn}" TargetType="Button">
|
||||
<Setter Property="Background" Value="#2E7D32"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="24,16">
|
||||
<StackPanel MaxWidth="900">
|
||||
|
||||
<!-- Header -->
|
||||
<DockPanel Margin="0,0,0,20">
|
||||
<TextBlock Text="Impostazioni" FontSize="22" FontWeight="Bold" Foreground="White" VerticalAlignment="Center"/>
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
|
||||
<Button Style="{StaticResource Btn}" Command="{Binding SaveCommand}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock Text="Salva" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Status -->
|
||||
<TextBlock Text="{Binding StatusMessage}" Foreground="#4CAF50" FontSize="11" Margin="0,0,0,16"/>
|
||||
|
||||
<!-- ═══ API CONFIGURATION ═══ -->
|
||||
<Border Style="{StaticResource SectionCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="CONFIGURAZIONE API" FontSize="11" FontWeight="Bold" Foreground="#546E7A" Margin="0,0,0,14"/>
|
||||
|
||||
<TextBlock Text="Preimpostazione" Style="{StaticResource Label}"/>
|
||||
<ComboBox ItemsSource="{Binding EndpointPresets}" SelectedItem="{Binding SelectedEndpointPreset}" Style="{StaticResource Combo}"/>
|
||||
|
||||
<TextBlock Text="API Endpoint" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding ApiEndpoint, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Modello" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding ModelName, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="API Key (vuoto per locale)" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding ApiKey, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Temperature" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding Temperature, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="Max Tokens" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding MaxTokens, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ═══ PROMPT EDITOR ═══ -->
|
||||
<Border Style="{StaticResource SectionCard}">
|
||||
<StackPanel>
|
||||
<DockPanel Margin="0,0,0,14">
|
||||
<TextBlock Text="PROMPT" FontSize="11" FontWeight="Bold" Foreground="#546E7A" VerticalAlignment="Center"/>
|
||||
<Button DockPanel.Dock="Right" Style="{StaticResource GreenBtn}" Command="{Binding InsertDefaultBettingPromptCommand}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="12" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock Text="Preset Blockchain Betting" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DockPanel>
|
||||
|
||||
<TextBlock Text="System Prompt" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding SystemPrompt, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource Field}" Height="160"
|
||||
AcceptsReturn="True" TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" VerticalContentAlignment="Top"/>
|
||||
|
||||
<TextBlock Text="User Prompt (trigger batch)" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding UserPrompt, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource Field}" Height="60"
|
||||
AcceptsReturn="True" TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" VerticalContentAlignment="Top"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ═══ OUTPUT ═══ -->
|
||||
<Border Style="{StaticResource SectionCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="OUTPUT" FontSize="11" FontWeight="Bold" Foreground="#546E7A" Margin="0,0,0,14"/>
|
||||
|
||||
<TextBlock Text="Cartella di Output" Style="{StaticResource Label}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Grid.Column="0" Text="{Binding OutputDirectory, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
<Button Grid.Column="2" Style="{StaticResource Btn}" Command="{Binding BrowseOutputDirectoryCommand}" Margin="0,0,0,10">
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Prefisso File (es. batch)" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding OutputFilePrefix, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="Dimensione Massima File (MB)" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding MaxFileSizeMb, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<TextBlock Foreground="#546E7A" FontSize="10"
|
||||
Text="I file vengono ruotati automaticamente quando raggiungono la dimensione massima (batch_001.jsonl, batch_002.jsonl, ...)"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ═══ TIMEOUT ═══ -->
|
||||
<Border Style="{StaticResource SectionCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="TIMEOUT" FontSize="11" FontWeight="Bold" Foreground="#546E7A" Margin="0,0,0,14"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Timeout Base (secondi)" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding ApiTimeoutSeconds, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="Secondi Aggiuntivi per 100 Token" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding TimeoutPerTokenRatio, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ═══ COSTI ═══ -->
|
||||
<Border Style="{StaticResource SectionCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="COSTI" FontSize="11" FontWeight="Bold" Foreground="#546E7A" Margin="0,0,0,14"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Elettricita (EUR/kWh)" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding ElectricityCostPerKwh, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="Potenza Sistema (Watt)" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding SystemPowerWatt, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="4">
|
||||
<TextBlock Text="Tipo Tariffazione API" Style="{StaticResource Label}"/>
|
||||
<ComboBox ItemsSource="{Binding ApiCostTypes}" SelectedItem="{Binding ApiCostType}" Style="{StaticResource Combo}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ═══ VERIFICA QUALITA ═══ -->
|
||||
<Border Style="{StaticResource SectionCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="VERIFICA QUALITA AI (opzionale)" FontSize="11" FontWeight="Bold" Foreground="#4CAF50" Margin="0,0,0,14"/>
|
||||
<CheckBox Content="Abilita verifica qualita AI" IsChecked="{Binding EnableQualityVerification}" Foreground="#90CAF9" FontSize="12" Margin="0,0,0,10"/>
|
||||
<CheckBox Content="Usa lo stesso modello/endpoint della generazione" IsChecked="{Binding UseSameModelForVerification}" Foreground="#78909C" FontSize="11" Margin="0,0,0,12"/>
|
||||
|
||||
<Grid IsEnabled="{Binding UseSameModelForVerification, Converter={StaticResource InverseBoolConverter}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Endpoint Verifica" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding VerificationApiEndpoint, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="Modello Verifica" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding VerificationModelName, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<TextBlock Text="API Key Verifica (se diversa)" Style="{StaticResource Label}"
|
||||
IsEnabled="{Binding UseSameModelForVerification, Converter={StaticResource InverseBoolConverter}}"/>
|
||||
<TextBox Text="{Binding VerificationApiKey, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}"
|
||||
IsEnabled="{Binding UseSameModelForVerification, Converter={StaticResource InverseBoolConverter}}"/>
|
||||
|
||||
<TextBlock Text="Valore Base per Record Alta Qualita (EUR)" Style="{StaticResource Label}"/>
|
||||
<TextBox Text="{Binding RevenuePerHighQualityRecord, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Field}" Width="200" HorizontalAlignment="Left"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- ═══ RESET ═══ -->
|
||||
<Border Style="{StaticResource SectionCard}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="RESET" FontSize="11" FontWeight="Bold" Foreground="#FF5722" Margin="0,0,0,14"/>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Style="{StaticResource DangerBtn}" Command="{Binding ResetTelemetryCommand}">
|
||||
<TextBlock Text="Elimina Telemetria"/>
|
||||
</Button>
|
||||
<Button Style="{StaticResource DangerBtn}" Command="{Binding ResetDatabaseCommand}">
|
||||
<TextBlock Text="Reset Completo DB"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Dione.Views
|
||||
{
|
||||
public partial class SettingsView : UserControl
|
||||
{
|
||||
public SettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<UserControl x:Class="Dione.Views.TelemetryHistoryView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Background="Transparent">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="ActionBtn" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Padding" Value="14,6"/>
|
||||
<Setter Property="Margin" Value="0,0,8,0"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="6" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- DataGrid styles -->
|
||||
<Style TargetType="DataGrid" x:Key="DarkGrid">
|
||||
<Setter Property="Background" Value="#0D0D14"/>
|
||||
<Setter Property="Foreground" Value="#E0E0E0"/>
|
||||
<Setter Property="BorderBrush" Value="#2A2A3E"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="RowBackground" Value="#12121A"/>
|
||||
<Setter Property="AlternatingRowBackground" Value="#16161E"/>
|
||||
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="#1E1E2E"/>
|
||||
<Setter Property="HeadersVisibility" Value="Column"/>
|
||||
<Setter Property="CanUserAddRows" Value="False"/>
|
||||
<Setter Property="CanUserDeleteRows" Value="False"/>
|
||||
<Setter Property="IsReadOnly" Value="True"/>
|
||||
<Setter Property="AutoGenerateColumns" Value="False"/>
|
||||
<Setter Property="SelectionMode" Value="Single"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
</Style>
|
||||
<Style TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="#1A1A26"/>
|
||||
<Setter Property="Foreground" Value="#90CAF9"/>
|
||||
<Setter Property="Padding" Value="8,6"/>
|
||||
<Setter Property="BorderBrush" Value="#2A2A3E"/>
|
||||
<Setter Property="BorderThickness" Value="0,0,1,1"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Padding" Value="6,4"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="#2962FF"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="24,16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="Telemetry History" FontSize="26" FontWeight="Bold" Foreground="White" Margin="0,0,0,16"/>
|
||||
|
||||
<!-- Filters -->
|
||||
<Border Grid.Row="1" Background="#1A1A26" CornerRadius="10" Padding="16" Margin="0,0,0,12">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Batch ID:" Foreground="#90CAF9" FontSize="12" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBox Text="{Binding FilterBatchId, UpdateSourceTrigger=PropertyChanged}" Width="120"
|
||||
Background="#1E1E2E" Foreground="White" BorderBrush="#2A2A3E" Padding="6,4" CaretBrush="White" VerticalContentAlignment="Center"/>
|
||||
|
||||
<TextBlock Text=" Model:" Foreground="#90CAF9" FontSize="12" VerticalAlignment="Center" Margin="16,0,6,0"/>
|
||||
<TextBox Text="{Binding FilterModel, UpdateSourceTrigger=PropertyChanged}" Width="120"
|
||||
Background="#1E1E2E" Foreground="White" BorderBrush="#2A2A3E" Padding="6,4" CaretBrush="White" VerticalContentAlignment="Center"/>
|
||||
|
||||
<TextBlock Text=" Status:" Foreground="#90CAF9" FontSize="12" VerticalAlignment="Center" Margin="16,0,6,0"/>
|
||||
<ComboBox ItemsSource="{Binding SuccessOptions}" SelectedItem="{Binding SelectedSuccessOption}" Width="90"
|
||||
Background="#1E1E2E" Foreground="White" BorderBrush="#2A2A3E" Padding="6,4"/>
|
||||
|
||||
<Button Content="Apply" Background="#2962FF" Command="{Binding ApplyFilterCommand}" Style="{StaticResource ActionBtn}" Margin="16,0,0,0"/>
|
||||
<Button Content="Clear" Background="#2A2A3E" Command="{Binding ClearFilterCommand}" Style="{StaticResource ActionBtn}"/>
|
||||
<Button Content="Refresh" Background="#2A2A3E" Command="{Binding RefreshCommand}" Style="{StaticResource ActionBtn}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Data Grid -->
|
||||
<DataGrid Grid.Row="2" ItemsSource="{Binding Logs}" Style="{StaticResource DarkGrid}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Width="50"/>
|
||||
<DataGridTextColumn Header="Timestamp" Binding="{Binding Timestamp, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="140"/>
|
||||
<DataGridTextColumn Header="Batch" Binding="{Binding BatchId}" Width="100"/>
|
||||
<DataGridTextColumn Header="Model" Binding="{Binding ModelUsed}" Width="120"/>
|
||||
<DataGridTextColumn Header="Prompt Tk" Binding="{Binding TokensPrompt}" Width="70"/>
|
||||
<DataGridTextColumn Header="Compl Tk" Binding="{Binding TokensCompletion}" Width="70"/>
|
||||
<DataGridTextColumn Header="Time (ms)" Binding="{Binding ExecutionTimeMs}" Width="75"/>
|
||||
<DataGridCheckBoxColumn Header="OK" Binding="{Binding IsSuccess, Mode=OneWay}" Width="40"/>
|
||||
<DataGridTextColumn Header="Error" Binding="{Binding ErrorMessage}" Width="200"/>
|
||||
<DataGridTextColumn Header="Preview" Binding="{Binding OutputPreview}" Width="300"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Status bar -->
|
||||
<DockPanel Grid.Row="3" Margin="0,8,0,0">
|
||||
<TextBlock Foreground="#78909C" FontSize="12" VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="Showing {0} of {1} records">
|
||||
<Binding Path="FilteredCount"/>
|
||||
<Binding Path="TotalCount"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{Binding StatusMessage}" Foreground="#4CAF50" FontSize="12" HorizontalAlignment="Right" DockPanel.Dock="Right"/>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Dione.Views
|
||||
{
|
||||
public partial class TelemetryHistoryView : UserControl
|
||||
{
|
||||
public TelemetryHistoryView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user