Add initial WinUI automation shell

This commit is contained in:
2026-06-08 23:54:51 +09:00
parent 19434e34db
commit 43066da7e9
28 changed files with 1315 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
bin/
obj/
.vs/
.vscode/
*.user
*.rsuser
*.suo
*.userosscache
*.sln.docstates
TestResults/
*.nupkg
*.snupkg
publish/
artifacts/

View File

@@ -0,0 +1,9 @@
<Solution>
<Configurations>
<Platform Name="x64" />
</Configurations>
<Project Path="TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj">
<Platform Solution="*|x64" Project="x64" />
<Deploy />
</Project>
</Solution>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Application
x:Class="TornadoAce_CJOnStyle.App"
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:local="using:TornadoAce_CJOnStyle"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="AppBackgroundBrush" Color="#0A0D12" />
<SolidColorBrush x:Key="AppChromeBrush" Color="#111827" />
<SolidColorBrush x:Key="PanelBrush" Color="#151A22" />
<SolidColorBrush x:Key="PanelAltBrush" Color="#1E2631" />
<SolidColorBrush x:Key="PanelStrokeBrush" Color="#303946" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#F6F8FB" />
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#C2CAD6" />
<SolidColorBrush x:Key="TextMutedBrush" Color="#8290A3" />
<SolidColorBrush x:Key="AccentCyanBrush" Color="#45D6D1" />
<SolidColorBrush x:Key="AccentGreenBrush" Color="#37D67A" />
<SolidColorBrush x:Key="AccentAmberBrush" Color="#FFB547" />
<SolidColorBrush x:Key="AccentRedBrush" Color="#FF5B61" />
<SolidColorBrush x:Key="AccentVioletBrush" Color="#B48CFF" />
<Style x:Key="PageTitleTextStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Bahnschrift SemiBold" />
<Setter Property="FontSize" Value="30" />
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
</Style>
<Style x:Key="SectionTitleTextStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Bahnschrift SemiBold" />
<Setter Property="FontSize" Value="18" />
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
</Style>
<Style x:Key="LabelTextStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Bahnschrift" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{StaticResource TextMutedBrush}" />
<Setter Property="TextWrapping" Value="WrapWholeWords" />
</Style>
<Style x:Key="BodyTextStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Bahnschrift" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="{StaticResource TextSecondaryBrush}" />
<Setter Property="TextWrapping" Value="WrapWholeWords" />
</Style>
<Style x:Key="MonoTextStyle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Cascadia Mono" />
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
</Style>
<Style x:Key="ActionButtonStyle" TargetType="Button">
<Setter Property="Padding" Value="12,8" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="FontFamily" Value="Bahnschrift SemiBold" />
</Style>
<Style x:Key="PrimaryActionButtonStyle"
TargetType="Button"
BasedOn="{StaticResource ActionButtonStyle}">
<Setter Property="Background" Value="{StaticResource AccentCyanBrush}" />
<Setter Property="Foreground" Value="#051012" />
</Style>
<Style x:Key="GhostActionButtonStyle"
TargetType="Button"
BasedOn="{StaticResource ActionButtonStyle}">
<Setter Property="Background" Value="{StaticResource PanelAltBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource PanelStrokeBrush}" />
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,52 @@
namespace TornadoAce_CJOnStyle
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
private Window? window;
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
window ??= new Window
{
Title = "TornadoAce CJ OnStyle CG Automation"
};
if (window.Content is not Frame rootFrame)
{
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
window.Content = rootFrame;
}
_ = rootFrame.Navigate(typeof(MainPage), e.Arguments);
window.Activate();
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,11 @@
namespace TornadoAce_CJOnStyle.Domain
{
public sealed record CueSheetRecord(
string CueSheetId,
DateTime BroadcastTime,
string ProgramTitle,
string ProductName,
string PriceText,
string CompositionText,
string PromotionText);
}

View File

@@ -0,0 +1,36 @@
using System.Text.Json;
namespace TornadoAce_CJOnStyle.Domain
{
public sealed record SceneTemplateDefinition(
string TemplateId,
string TemplateName,
string TornadoVersion,
IReadOnlyList<FieldMappingDefinition> Mappings);
public sealed record FieldMappingDefinition(
string CueSheetField,
string SceneObjectName,
string Attribute,
string Rule);
public sealed record SceneDataSnapshot(
string SceneName,
string TemplateId,
IReadOnlyDictionary<string, string> VisibleTextObjects,
IReadOnlyList<string> ImagePaths)
{
public string ToPreviewJson()
{
var payload = new
{
scene = SceneName,
templateId = TemplateId,
text = VisibleTextObjects,
images = ImagePaths
};
return JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
}
}
}

View File

@@ -0,0 +1,10 @@
namespace TornadoAce_CJOnStyle.Domain
{
public sealed record InspectionResult(
string CueSheetId,
string ProgramTitle,
string SceneName,
double MatchRate,
string Severity,
string Decision);
}

View File

@@ -0,0 +1,10 @@
global using CommunityToolkit.Mvvm.ComponentModel;
global using CommunityToolkit.Mvvm.Input;
global using CommunityToolkit.Mvvm.Messaging;
global using TornadoAce_CJOnStyle.ViewModels;
global using TornadoAce_CJOnStyle.Views;
global using Microsoft.UI.Xaml;
global using Microsoft.UI.Xaml.Controls;
global using Microsoft.UI.Xaml.Navigation;

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity
Name="e3cb6767-25dc-4651-b094-b6efbe33b802"
Publisher="CN=Comtropy"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="e3cb6767-25dc-4651-b094-b6efbe33b802" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>TornadoAce CJ OnStyle</DisplayName>
<PublisherDisplayName>Comtropy</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="TornadoAce CJ OnStyle"
Description="CJ OnStyle CG production automation console"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishProtocol>FileSystem</PublishProtocol>
<Platform>x64</Platform>
<RuntimeIdentifier Condition="$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)')) >= 8">win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)')) &lt; 8">win10-x64</RuntimeIdentifier>
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"TornadoAce_CJOnStyle (Package)": {
"commandName": "MsixPackage"
},
"TornadoAce_CJOnStyle (Unpackaged)": {
"commandName": "Project"
}
}
}

View File

@@ -0,0 +1,70 @@
using TornadoAce_CJOnStyle.Domain;
namespace TornadoAce_CJOnStyle.Services
{
public sealed class AutomationWorkspace
{
private AutomationWorkspace(
IReadOnlyList<CueSheetRecord> cueSheets,
SceneTemplateDefinition activeTemplate,
SceneDataSnapshot activeScene,
IReadOnlyList<InspectionResult> inspectionResults)
{
CueSheets = cueSheets;
ActiveTemplate = activeTemplate;
ActiveScene = activeScene;
InspectionResults = inspectionResults;
}
public IReadOnlyList<CueSheetRecord> CueSheets { get; }
public SceneTemplateDefinition ActiveTemplate { get; }
public SceneDataSnapshot ActiveScene { get; }
public IReadOnlyList<InspectionResult> InspectionResults { get; }
public static AutomationWorkspace CreateDemo()
{
var cueSheets = new[]
{
new CueSheetRecord("CJ-20260817-001", DateTime.Today.AddHours(9), "최화정쇼", "비비안 컴포트 패키지", "79,900원", "브라 4종 + 팬티 4종", "ARS 10% 할인"),
new CueSheetRecord("CJ-20260817-002", DateTime.Today.AddHours(11), "동가게", "프리미엄 한우 세트", "69,900원", "1등급 등심 1.2kg", "무이자 5개월"),
new CueSheetRecord("CJ-20260817-003", DateTime.Today.AddHours(14), "스타일 라이브", "썸머 린넨 셋업", "59,900원", "자켓 + 팬츠", "앱 주문 추가 적립")
};
var mappings = new[]
{
new FieldMappingDefinition("programTitle", "TXT_PROGRAM", "Text", "trim"),
new FieldMappingDefinition("productName", "TXT_PRODUCT", "Text", "normalize-space"),
new FieldMappingDefinition("priceText", "TXT_PRICE", "Text", "currency-text"),
new FieldMappingDefinition("compositionText", "TXT_COMPOSITION", "Text", "line-break-safe"),
new FieldMappingDefinition("promotionText", "TXT_PROMOTION", "Text", "special-char-exception")
};
var template = new SceneTemplateDefinition("CJ_PRICE_LOWER_01", "가격형 하단 자막 / Tornado Scene", "Tornado2 primary, Tornado3 ready", mappings);
var scene = new SceneDataSnapshot(
"CJ_PRICE_LOWER_01_0900.tscn",
template.TemplateId,
new Dictionary<string, string>
{
["TXT_PROGRAM"] = "최화정쇼",
["TXT_PRODUCT"] = "비비안 컴포트 패키지",
["TXT_PRICE"] = "79,900원",
["TXT_COMPOSITION"] = "브라 4종 + 팬티 4종",
["TXT_PROMOTION"] = "ARS 10% 할인"
},
new[] { @"D:\CJOnStyle\Products\VIVIEN\main.png" });
var inspections = new[]
{
new InspectionResult("CJ-20260817-001", "최화정쇼", "CJ_PRICE_LOWER_01_0900.tscn", 100, "Low", "1차 통과"),
new InspectionResult("CJ-20260817-002", "동가게", "CJ_PRICE_LOWER_01_1100.tscn", 96.4, "High", "가격 불일치 확인"),
new InspectionResult("CJ-20260817-003", "스타일 라이브", "CJ_PRICE_LOWER_01_1400.tscn", 98.1, "Medium", "특수문자 예외 후보")
};
return new AutomationWorkspace(cueSheets, template, scene, inspections);
}
}
}

View File

@@ -0,0 +1,21 @@
using TornadoAce_CJOnStyle.Domain;
namespace TornadoAce_CJOnStyle.Services
{
public interface ICueSheetClient
{
Task<IReadOnlyList<CueSheetRecord>> GetCueSheetsAsync(DateOnly broadcastDate, CancellationToken cancellationToken);
}
public interface ITornadoSceneAdapter
{
Task<SceneDataSnapshot> CreateOrUpdateSceneAsync(CueSheetRecord cueSheet, SceneTemplateDefinition template, CancellationToken cancellationToken);
Task<SceneDataSnapshot> ExtractSceneDataAsync(string scenePath, CancellationToken cancellationToken);
}
public interface IInspectionService
{
Task<InspectionResult> InspectAsync(CueSheetRecord cueSheet, SceneDataSnapshot sceneSnapshot, CancellationToken cancellationToken);
}
}

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<OutputType>WinExe</OutputType>
<!-- WinUI 3 -->
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<!-- <WindowsSdkPackageVersion>10.0.19041.38</WindowsSdkPackageVersion> -->
<!-- Project Options -->
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>TornadoAce_CJOnStyle</RootNamespace>
<!-- App Options -->
<UseRidGraph>true</UseRidGraph>
<Platforms>x64</Platforms>
<ApplicationManifest>app.manifest</ApplicationManifest>
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<None Remove="TornadoAce_CJOnStyle.slnx" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.260209005" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored.
-->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix"/>
</ItemGroup>
<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
Explorer "Package and Publish" context menu entry to be enabled for this project even if
the Windows App SDK Nuget package has not yet been restored.
-->
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,13 @@
namespace TornadoAce_CJOnStyle.ViewModels
{
public partial class BaseViewModel : ObservableObject
{
private string title = string.Empty;
public string Title
{
get => title;
set => SetProperty(ref title, value);
}
}
}

View File

@@ -0,0 +1,202 @@
using Microsoft.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System.Collections.ObjectModel;
using TornadoAce_CJOnStyle.Services;
using Windows.UI;
namespace TornadoAce_CJOnStyle.ViewModels
{
public partial class MainViewModel : BaseViewModel
{
private readonly AutomationWorkspace workspace;
public MainViewModel()
{
Title = "CG 제작 자동화";
workspace = AutomationWorkspace.CreateDemo();
BuildPhase = "INITIAL BUILD";
OperatorMessage = "큐시트 플랫폼, Tornado Scene 템플릿, 자동 검수 결과를 한 화면에서 확인하는 운영 콘솔 초안입니다.";
LastCueSheetSyncText = "목 데이터 기준 13:25";
ActiveTemplateName = workspace.ActiveTemplate.TemplateName;
InspectionSummary = "1차 diff 실패 2건 / LLM 검토 대기 1건";
SceneSnapshotLabel = $"{workspace.ActiveScene.SceneName} / visible text objects";
SceneSnapshotText = workspace.ActiveScene.ToPreviewJson();
LlmPolicyText = "사내 LLM 우선, 외부 서비스는 정책 승인 후";
LlmPolicyDetail = "1차 Text Diffing 실패 건만 2차 LLM 판정으로 이동하고, 가격·구성·방송시간 변경은 높은 중요도로 표시합니다.";
LoadStatusCards();
LoadPipelineSteps();
LoadFieldMappings();
LoadEndpoints();
LoadInspectionQueue();
}
private string buildPhase = string.Empty;
private string operatorMessage = string.Empty;
private string lastCueSheetSyncText = string.Empty;
private string activeTemplateName = string.Empty;
private string inspectionSummary = string.Empty;
private string sceneSnapshotLabel = string.Empty;
private string sceneSnapshotText = string.Empty;
private string llmPolicyText = string.Empty;
private string llmPolicyDetail = string.Empty;
public string BuildPhase
{
get => buildPhase;
set => SetProperty(ref buildPhase, value);
}
public string OperatorMessage
{
get => operatorMessage;
set => SetProperty(ref operatorMessage, value);
}
public string LastCueSheetSyncText
{
get => lastCueSheetSyncText;
set => SetProperty(ref lastCueSheetSyncText, value);
}
public string ActiveTemplateName
{
get => activeTemplateName;
set => SetProperty(ref activeTemplateName, value);
}
public string InspectionSummary
{
get => inspectionSummary;
set => SetProperty(ref inspectionSummary, value);
}
public string SceneSnapshotLabel
{
get => sceneSnapshotLabel;
set => SetProperty(ref sceneSnapshotLabel, value);
}
public string SceneSnapshotText
{
get => sceneSnapshotText;
set => SetProperty(ref sceneSnapshotText, value);
}
public string LlmPolicyText
{
get => llmPolicyText;
set => SetProperty(ref llmPolicyText, value);
}
public string LlmPolicyDetail
{
get => llmPolicyDetail;
set => SetProperty(ref llmPolicyDetail, value);
}
public ObservableCollection<StatusCardViewModel> StatusCards { get; } = [];
public ObservableCollection<PipelineStepViewModel> PipelineSteps { get; } = [];
public ObservableCollection<FieldMappingViewModel> FieldMappings { get; } = [];
public ObservableCollection<IntegrationEndpointViewModel> Endpoints { get; } = [];
public ObservableCollection<InspectionQueueItemViewModel> InspectionQueue { get; } = [];
[RelayCommand]
private void RefreshCueSheet()
{
LastCueSheetSyncText = $"목 데이터 동기화 {DateTime.Now:HH:mm:ss}";
OperatorMessage = "큐시트 목 데이터를 다시 읽었습니다. 실제 API 연결 전까지 이 버튼은 화면 흐름 확인용으로 동작합니다.";
StatusCards[0].Metric = "12";
StatusCards[0].Detail = "동기화 완료";
}
[RelayCommand]
private void GenerateScene()
{
OperatorMessage = "선택된 큐시트 레코드를 Tornado Scene 템플릿에 매핑하는 흐름을 확인했습니다.";
StatusCards[1].Metric = "7";
StatusCards[1].Detail = "생성 후보";
}
[RelayCommand]
private void RunInspection()
{
OperatorMessage = "Scene 데이터와 큐시트 데이터를 비교하는 1차 검수 흐름을 확인했습니다.";
InspectionSummary = "1차 diff 실패 2건 / LLM 검토 대기 1건 / 통과 5건";
StatusCards[2].Metric = "99.2%";
StatusCards[2].Detail = "평균 일치율";
}
private void LoadStatusCards()
{
StatusCards.Add(new StatusCardViewModel("큐시트 API", "12", "수신 대기", Symbol.Sync, Brush(0xFF45D6D1)));
StatusCards.Add(new StatusCardViewModel("Scene 매핑", "5", "템플릿 정의", Symbol.Map, Brush(0xFFB48CFF)));
StatusCards.Add(new StatusCardViewModel("자동 검수", "98.7%", "목표 99%+", Symbol.Accept, Brush(0xFFFFB547)));
StatusCards.Add(new StatusCardViewModel("Tornado", "Mock", "SDK 어댑터 자리", Symbol.Repair, Brush(0xFF37D67A)));
}
private void LoadPipelineSteps()
{
PipelineSteps.Add(new PipelineStepViewModel("01", "큐시트 데이터 수신", "AI ON Studio / 자막 큐시트 API", "설계", 35, Brush(0xFF45D6D1)));
PipelineSteps.Add(new PipelineStepViewModel("02", "Data Field Mapping", "큐시트 필드 -> Tornado Scene 객체", "초안", 45, Brush(0xFFB48CFF)));
PipelineSteps.Add(new PipelineStepViewModel("03", "Scene 생성/갱신", "Tornado2 우선, Tornado3 어댑터 준비", "대기", 25, Brush(0xFF37D67A)));
PipelineSteps.Add(new PipelineStepViewModel("04", "Scene Data API 추출", "Text/Image visible object snapshot", "초안", 30, Brush(0xFFFFB547)));
PipelineSteps.Add(new PipelineStepViewModel("05", "자동 검수 피드백", "Text Diffing + LLM 중요도 판정", "초안", 40, Brush(0xFFFF5B61)));
}
private void LoadFieldMappings()
{
foreach (var mapping in workspace.ActiveTemplate.Mappings)
{
FieldMappings.Add(new FieldMappingViewModel(mapping.CueSheetField, mapping.SceneObjectName, mapping.Rule, mapping.Attribute));
}
}
private void LoadEndpoints()
{
Endpoints.Add(new IntegrationEndpointViewModel("큐시트 플랫폼", "GET /api/cuesheets/{broadcastDate}", "AI ON Studio", "Mock", Brush(0xFF45D6D1)));
Endpoints.Add(new IntegrationEndpointViewModel("ERP 편성", "GET /api/schedules/{programCode}", "CJ OnStyle ERP", "대기", Brush(0xFFFFB547)));
Endpoints.Add(new IntegrationEndpointViewModel("Tornado Scene API", "POST /api/scenes/extract", "Tornado Adapter", "설계", Brush(0xFF37D67A)));
Endpoints.Add(new IntegrationEndpointViewModel("LLM 검수", "POST /api/inspection/llm", "Internal LLM", "정책 확인", Brush(0xFFB48CFF)));
}
private void LoadInspectionQueue()
{
foreach (var item in workspace.InspectionResults)
{
InspectionQueue.Add(new InspectionQueueItemViewModel(
item.ProgramTitle,
item.SceneName,
$"{item.MatchRate:0.0}%",
item.Severity,
item.Decision,
SeverityBrush(item.Severity)));
}
}
private static SolidColorBrush SeverityBrush(string severity)
{
return severity switch
{
"High" => Brush(0xFFFF5B61),
"Medium" => Brush(0xFFFFB547),
_ => Brush(0xFF37D67A)
};
}
private static SolidColorBrush Brush(uint argb)
{
return new SolidColorBrush(Color.FromArgb(
(byte)(argb >> 24),
(byte)(argb >> 16),
(byte)(argb >> 8),
(byte)argb));
}
}
}

View File

@@ -0,0 +1,70 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
namespace TornadoAce_CJOnStyle.ViewModels
{
public sealed partial class StatusCardViewModel : ObservableObject
{
private string metric = string.Empty;
private string detail = string.Empty;
public StatusCardViewModel(string label, string metric, string detail, Symbol icon, SolidColorBrush accentBrush)
{
Label = label;
Metric = metric;
Detail = detail;
Icon = icon;
AccentBrush = accentBrush;
}
public string Label { get; }
public Symbol Icon { get; }
public SolidColorBrush AccentBrush { get; }
public string Metric
{
get => metric;
set => SetProperty(ref metric, value);
}
public string Detail
{
get => detail;
set => SetProperty(ref detail, value);
}
}
public sealed record PipelineStepViewModel(
string StepNumber,
string Title,
string SystemName,
string Status,
int Completion,
SolidColorBrush AccentBrush);
public sealed record FieldMappingViewModel(
string CueSheetField,
string SceneObject,
string BindingRule,
string Attribute);
public sealed record IntegrationEndpointViewModel(
string Name,
string Endpoint,
string Owner,
string Status,
SolidColorBrush StatusBrush)
{
public string OwnerAndStatus => $"{Owner} / {Status}";
}
public sealed record InspectionQueueItemViewModel(
string ProgramTitle,
string SceneName,
string MatchRate,
string Severity,
string Decision,
SolidColorBrush SeverityBrush);
}

View File

@@ -0,0 +1,499 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Page
x:Class="TornadoAce_CJOnStyle.Views.MainPage"
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:vm="using:TornadoAce_CJOnStyle.ViewModels"
mc:Ignorable="d">
<Page.Content>
<NavigationView
AlwaysShowHeader="False"
Background="{StaticResource AppChromeBrush}"
IsBackButtonVisible="Collapsed"
IsSettingsVisible="False"
PaneDisplayMode="LeftCompact"
RequestedTheme="Dark">
<NavigationView.PaneHeader>
<StackPanel Margin="12,18,12,12" Spacing="4">
<TextBlock Style="{StaticResource LabelTextStyle}" Text="TORNADOACE" />
<TextBlock
FontFamily="Bahnschrift SemiBold"
FontSize="16"
Foreground="{StaticResource TextPrimaryBrush}"
Text="CJ OnStyle" />
<TextBlock
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind ViewModel.BuildPhase, Mode=OneWay}" />
</StackPanel>
</NavigationView.PaneHeader>
<NavigationView.MenuItems>
<NavigationViewItem Content="대시보드" IsSelected="True">
<NavigationViewItem.Icon>
<SymbolIcon Symbol="Home" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="큐시트">
<NavigationViewItem.Icon>
<SymbolIcon Symbol="Library" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="매핑">
<NavigationViewItem.Icon>
<SymbolIcon Symbol="Map" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="검수">
<NavigationViewItem.Icon>
<SymbolIcon Symbol="Accept" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="연동">
<NavigationViewItem.Icon>
<SymbolIcon Symbol="Sync" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="설정">
<NavigationViewItem.Icon>
<SymbolIcon Symbol="Setting" />
</NavigationViewItem.Icon>
</NavigationViewItem>
</NavigationView.MenuItems>
<ScrollViewer
Background="{StaticResource AppBackgroundBrush}"
HorizontalScrollBarVisibility="Auto"
HorizontalScrollMode="Auto"
VerticalScrollBarVisibility="Auto"
VerticalScrollMode="Auto">
<Grid
MinWidth="1180"
Margin="24"
RowSpacing="18">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Spacing="6">
<TextBlock
Style="{StaticResource PageTitleTextStyle}"
Text="CG 제작 자동화 컨트롤" />
<TextBlock
MaxWidth="880"
Style="{StaticResource BodyTextStyle}"
Text="{x:Bind ViewModel.OperatorMessage, Mode=OneWay}" />
</StackPanel>
<StackPanel
Grid.Column="1"
Orientation="Horizontal"
Spacing="10"
VerticalAlignment="Top">
<Button
Command="{x:Bind ViewModel.RefreshCueSheetCommand}"
Style="{StaticResource GhostActionButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="7">
<SymbolIcon Symbol="Refresh" />
<TextBlock Text="큐시트 동기화" />
</StackPanel>
</Button>
<Button
Command="{x:Bind ViewModel.GenerateSceneCommand}"
Style="{StaticResource PrimaryActionButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="7">
<SymbolIcon Symbol="Play" />
<TextBlock Text="Scene 생성" />
</StackPanel>
</Button>
<Button
Command="{x:Bind ViewModel.RunInspectionCommand}"
Style="{StaticResource GhostActionButtonStyle}">
<StackPanel Orientation="Horizontal" Spacing="7">
<SymbolIcon Symbol="Accept" />
<TextBlock Text="검수 실행" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
<ItemsControl
Grid.Row="1"
ItemsSource="{x:Bind ViewModel.StatusCards, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:StatusCardViewModel">
<Border
Width="268"
Margin="0,0,12,0"
Padding="14"
Background="{StaticResource PanelBrush}"
BorderBrush="{StaticResource PanelStrokeBrush}"
BorderThickness="1"
CornerRadius="8">
<Grid ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Width="34"
Height="34"
Background="{x:Bind AccentBrush}"
CornerRadius="8">
<SymbolIcon
Foreground="#071013"
Symbol="{x:Bind Icon}" />
</Border>
<StackPanel Grid.Column="1" Spacing="4">
<TextBlock
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind Label}" />
<TextBlock
FontFamily="Bahnschrift SemiBold"
FontSize="24"
Foreground="{StaticResource TextPrimaryBrush}"
Text="{x:Bind Metric}" />
<TextBlock
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind Detail}" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid
Grid.Row="2"
ColumnSpacing="16"
RowSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.2*" />
<ColumnDefinition Width="1.4*" />
<ColumnDefinition Width="1.1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="380" />
<RowDefinition Height="330" />
</Grid.RowDefinitions>
<Border
Padding="16"
Background="{StaticResource PanelBrush}"
BorderBrush="{StaticResource PanelStrokeBrush}"
BorderThickness="1"
CornerRadius="8">
<StackPanel Spacing="14">
<Grid>
<TextBlock Style="{StaticResource SectionTitleTextStyle}" Text="업무 파이프라인" />
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind ViewModel.LastCueSheetSyncText, Mode=OneWay}" />
</Grid>
<ListView
ItemsSource="{x:Bind ViewModel.PipelineSteps, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:PipelineStepViewModel">
<Grid
Margin="0,0,0,8"
ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="42" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border
Width="34"
Height="34"
Background="{x:Bind AccentBrush}"
CornerRadius="8">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="Cascadia Mono"
Foreground="#071013"
Text="{x:Bind StepNumber}" />
</Border>
<StackPanel Grid.Column="1" Spacing="5">
<Grid>
<TextBlock
FontFamily="Bahnschrift SemiBold"
Foreground="{StaticResource TextPrimaryBrush}"
Text="{x:Bind Title}" />
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind Status}" />
</Grid>
<TextBlock
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind SystemName}" />
<ProgressBar
Height="6"
Maximum="100"
Value="{x:Bind Completion}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
<Border
Grid.Column="1"
Padding="16"
Background="{StaticResource PanelBrush}"
BorderBrush="{StaticResource PanelStrokeBrush}"
BorderThickness="1"
CornerRadius="8">
<StackPanel Spacing="14">
<Grid>
<TextBlock Style="{StaticResource SectionTitleTextStyle}" Text="Scene 필드 매핑" />
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind ViewModel.ActiveTemplateName, Mode=OneWay}" />
</Grid>
<Border
Padding="10,8"
Background="{StaticResource PanelAltBrush}"
CornerRadius="8">
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.1*" />
<ColumnDefinition Width="1.2*" />
<ColumnDefinition Width="1.1*" />
<ColumnDefinition Width="0.8*" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource LabelTextStyle}" Text="큐시트 필드" />
<TextBlock Grid.Column="1" Style="{StaticResource LabelTextStyle}" Text="Tornado 객체" />
<TextBlock Grid.Column="2" Style="{StaticResource LabelTextStyle}" Text="매핑 규칙" />
<TextBlock Grid.Column="3" Style="{StaticResource LabelTextStyle}" Text="속성" />
</Grid>
</Border>
<ListView
ItemsSource="{x:Bind ViewModel.FieldMappings, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:FieldMappingViewModel">
<Border Padding="10,7">
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.1*" />
<ColumnDefinition Width="1.2*" />
<ColumnDefinition Width="1.1*" />
<ColumnDefinition Width="0.8*" />
</Grid.ColumnDefinitions>
<TextBlock
Style="{StaticResource MonoTextStyle}"
Text="{x:Bind CueSheetField}" />
<TextBlock
Grid.Column="1"
Style="{StaticResource BodyTextStyle}"
Text="{x:Bind SceneObject}" />
<TextBlock
Grid.Column="2"
Style="{StaticResource BodyTextStyle}"
Text="{x:Bind BindingRule}" />
<TextBlock
Grid.Column="3"
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind Attribute}" />
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
<Border
Grid.Column="2"
Padding="16"
Background="{StaticResource PanelBrush}"
BorderBrush="{StaticResource PanelStrokeBrush}"
BorderThickness="1"
CornerRadius="8">
<StackPanel Spacing="14">
<TextBlock Style="{StaticResource SectionTitleTextStyle}" Text="연동 엔드포인트" />
<ItemsControl ItemsSource="{x:Bind ViewModel.Endpoints, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:IntegrationEndpointViewModel">
<Grid
Margin="0,0,0,10"
ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse
Width="10"
Height="10"
Margin="0,5,0,0"
Fill="{x:Bind StatusBrush}" />
<StackPanel Grid.Column="1" Spacing="3">
<TextBlock
FontFamily="Bahnschrift SemiBold"
Foreground="{StaticResource TextPrimaryBrush}"
Text="{x:Bind Name}" />
<TextBlock
Style="{StaticResource MonoTextStyle}"
Text="{x:Bind Endpoint}" />
<TextBlock
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind OwnerAndStatus}" />
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<Border
Grid.Row="1"
Grid.ColumnSpan="2"
Padding="16"
Background="{StaticResource PanelBrush}"
BorderBrush="{StaticResource PanelStrokeBrush}"
BorderThickness="1"
CornerRadius="8">
<StackPanel Spacing="14">
<Grid>
<TextBlock Style="{StaticResource SectionTitleTextStyle}" Text="자동 검수 큐" />
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind ViewModel.InspectionSummary, Mode=OneWay}" />
</Grid>
<Border
Padding="10,8"
Background="{StaticResource PanelAltBrush}"
CornerRadius="8">
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.1*" />
<ColumnDefinition Width="1.2*" />
<ColumnDefinition Width="0.7*" />
<ColumnDefinition Width="0.9*" />
<ColumnDefinition Width="1.1*" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource LabelTextStyle}" Text="프로그램" />
<TextBlock Grid.Column="1" Style="{StaticResource LabelTextStyle}" Text="Scene" />
<TextBlock Grid.Column="2" Style="{StaticResource LabelTextStyle}" Text="일치율" />
<TextBlock Grid.Column="3" Style="{StaticResource LabelTextStyle}" Text="중요도" />
<TextBlock Grid.Column="4" Style="{StaticResource LabelTextStyle}" Text="판정" />
</Grid>
</Border>
<ListView
ItemsSource="{x:Bind ViewModel.InspectionQueue, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:InspectionQueueItemViewModel">
<Border Padding="10,8">
<Grid ColumnSpacing="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1.1*" />
<ColumnDefinition Width="1.2*" />
<ColumnDefinition Width="0.7*" />
<ColumnDefinition Width="0.9*" />
<ColumnDefinition Width="1.1*" />
</Grid.ColumnDefinitions>
<TextBlock
Style="{StaticResource BodyTextStyle}"
Text="{x:Bind ProgramTitle}" />
<TextBlock
Grid.Column="1"
Style="{StaticResource MonoTextStyle}"
Text="{x:Bind SceneName}" />
<TextBlock
Grid.Column="2"
FontFamily="Cascadia Mono"
Foreground="{StaticResource TextPrimaryBrush}"
Text="{x:Bind MatchRate}" />
<Border
Grid.Column="3"
Padding="8,3"
HorizontalAlignment="Left"
Background="{x:Bind SeverityBrush}"
CornerRadius="8">
<TextBlock
FontFamily="Bahnschrift SemiBold"
FontSize="12"
Foreground="#071013"
Text="{x:Bind Severity}" />
</Border>
<TextBlock
Grid.Column="4"
Style="{StaticResource BodyTextStyle}"
Text="{x:Bind Decision}" />
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Border>
<Border
Grid.Row="1"
Grid.Column="2"
Padding="16"
Background="{StaticResource PanelBrush}"
BorderBrush="{StaticResource PanelStrokeBrush}"
BorderThickness="1"
CornerRadius="8">
<StackPanel Spacing="14">
<TextBlock Style="{StaticResource SectionTitleTextStyle}" Text="Scene 데이터 API 샘플" />
<Border
Padding="14"
Background="#101419"
BorderBrush="{StaticResource PanelStrokeBrush}"
BorderThickness="1"
CornerRadius="8">
<StackPanel Spacing="8">
<TextBlock
Style="{StaticResource LabelTextStyle}"
Text="{x:Bind ViewModel.SceneSnapshotLabel, Mode=OneWay}" />
<TextBlock
FontFamily="Cascadia Mono"
FontSize="13"
Foreground="{StaticResource AccentCyanBrush}"
Text="{x:Bind ViewModel.SceneSnapshotText, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
</StackPanel>
</Border>
<StackPanel Spacing="6">
<TextBlock Style="{StaticResource LabelTextStyle}" Text="LLM 검수 정책" />
<TextBlock
FontFamily="Bahnschrift SemiBold"
FontSize="18"
Foreground="{StaticResource TextPrimaryBrush}"
Text="{x:Bind ViewModel.LlmPolicyText, Mode=OneWay}" />
<TextBlock
Style="{StaticResource BodyTextStyle}"
Text="{x:Bind ViewModel.LlmPolicyDetail, Mode=OneWay}" />
</StackPanel>
</StackPanel>
</Border>
</Grid>
</Grid>
</ScrollViewer>
</NavigationView>
</Page.Content>
</Page>

View File

@@ -0,0 +1,17 @@
namespace TornadoAce_CJOnStyle.Views
{
/// <summary>
/// A simple page that can be used on its own or navigated to within a Frame.
/// </summary>
public partial class MainPage : Page
{
private readonly MainViewModel viewModel = new();
public MainPage()
{
this.InitializeComponent();
}
public MainViewModel ViewModel => viewModel;
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="TornadoAce_CJOnStyle.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below informs the system that this application is compatible with OS features first introduced in Windows 8.
For more info see https://learn.microsoft.com/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
It is also necessary to support features in unpackaged applications, for example the custom titlebar implementation.-->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
</assembly>

View File

@@ -0,0 +1,24 @@
# TornadoAce CJ OnStyle 초기 구조
## RFP에서 잡은 핵심 흐름
1. 큐시트 플랫폼과 ERP 편성 데이터를 API로 수신한다.
2. 큐시트 필드를 사전 정의된 Tornado Scene 템플릿 객체에 매핑한다.
3. Tornado Scene에서 화면에 노출되는 Text/Image 데이터를 추출하는 API를 둔다.
4. 큐시트 데이터와 Scene 데이터를 1차 Text Diffing으로 비교한다.
5. 1차 실패 건은 LLM 검수로 중요도를 판정하고 제작자에게 피드백한다.
## 현재 초기 빌드 범위
- `Domain`: 큐시트, Scene 템플릿, Scene 데이터 스냅샷, 검수 결과 모델.
- `Services`: 실제 API/Tornado SDK 연결을 받을 인터페이스와 목 워크스페이스.
- `ViewModels`: 운영 콘솔에 표시할 상태 카드, 파이프라인, 매핑표, 검수 큐.
- `Views`: WinUI 3 기반 첫 화면. 예제 프로젝트와 같은 데스크톱 앱 방향으로 유지.
## 다음 설계 결정 지점
- Tornado2와 Tornado3의 어댑터 경계를 같은 인터페이스로 유지할지 여부.
- Scene 내부 비노출 객체를 구분할 메타데이터 규칙.
- 큐시트 API 원문 필드명과 화면용 정규화 필드명 분리 방식.
- 특수문자 예외 처리 목록을 룰 파일로 둘지 관리자 UI에서 편집할지 여부.
- LLM이 반환할 중요도 스키마와 감사 로그 보관 범위.