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:
2026-04-21 23:19:50 +02:00
parent c4f39e7e14
commit a768fb8e04
42 changed files with 4027 additions and 0 deletions
+6
View File
@@ -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>
+22
View File
@@ -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>
+14
View File
@@ -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();
}
}
}
+23
View File
@@ -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;
}
}
}
+294
View File
@@ -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();
}
}
}
+187
View File
@@ -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>
+3
View File
@@ -0,0 +1,3 @@
<Solution>
<Project Path="Dione.csproj" Id="c455f7db-c47e-4d71-87d6-9c122f18f986" />
</Solution>
View File
View File
View File
View File
+17
View File
@@ -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>
+182
View File
@@ -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="&#xE9F9;" 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="&#xE768;" 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="&#xE9D9;" 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="&#xE713;" 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="&#xE921;" 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="&#xE922;" 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="&#xE8BB;" 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>
+91
View File
@@ -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;
}
}
}
+46
View File
@@ -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;
}
}
+110
View File
@@ -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>();
}
}
+25
View File
@@ -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; }
}
}
+39
View File
@@ -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; }
}
}
+71
View File
@@ -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;
}
}
}
}
+117
View File
@@ -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>
+30
View File
@@ -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;
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
+199
View File
@@ -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
}
}
}
}
+586
View File
@@ -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);
}
}
}
+79
View File
@@ -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;
}
}
}
+303
View File
@@ -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();
}
}
}
+161
View File
@@ -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="&#xE72C;" 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="&#xE9CE;" 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>
+12
View File
@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Dione.Views
{
public partial class DashboardView : UserControl
{
public DashboardView()
{
InitializeComponent();
}
}
}
+115
View File
@@ -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>
+12
View File
@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Dione.Views
{
public partial class DataDesignerView : UserControl
{
public DataDesignerView()
{
InitializeComponent();
}
}
}
+643
View File
@@ -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="&#xE916;" 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="&#xE768;" 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="&#xE71A;" 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="&#xE930;" 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="&#xE783;" 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="&#xE9F9;" 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="&#xE8D2;" 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="&#xE8B7;" 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="&#xE916;" 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="&#xE8B7;" 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="&#xE943;" 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="&#xE8D2;" 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="&#xE8AC;" 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="&#xE9D9;" 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="&#xE8C8;" FontFamily="Segoe MDL2 Assets" FontSize="13"/>
</Button>
<!-- Pulisci log -->
<Button Command="{Binding ClearLogCommand}" Style="{StaticResource IconBtn}"
ToolTip="Pulisci log">
<TextBlock Text="&#xE74D;" 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>
+38
View File
@@ -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();
}
}
}
}
+14
View File
@@ -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="&#xE946;" 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>
+17
View File
@@ -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;
}
}
}
+292
View File
@@ -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="&#xE74E;" 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="&#xE756;" 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="&#xE838;" 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>
+12
View File
@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Dione.Views
{
public partial class SettingsView : UserControl
{
public SettingsView()
{
InitializeComponent();
}
}
}
+124
View File
@@ -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>
+12
View File
@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace Dione.Views
{
public partial class TelemetryHistoryView : UserControl
{
public TelemetryHistoryView()
{
InitializeComponent();
}
}
}