From 43066da7e9b6454d1596b6e93a8d5c07595fe7fb Mon Sep 17 00:00:00 2001 From: Wickedness Date: Mon, 8 Jun 2026 23:54:51 +0900 Subject: [PATCH] Add initial WinUI automation shell --- .gitignore | 17 + TornadoAce_CJOnStyle.slnx | 9 + TornadoAce_CJOnStyle/App.xaml | 83 +++ TornadoAce_CJOnStyle/App.xaml.cs | 52 ++ .../Assets/LockScreenLogo.scale-200.png | Bin 0 -> 432 bytes .../Assets/SplashScreen.scale-200.png | Bin 0 -> 5372 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 1755 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 637 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 283 bytes TornadoAce_CJOnStyle/Assets/StoreLogo.png | Bin 0 -> 456 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 2097 bytes TornadoAce_CJOnStyle/Domain/CueSheetModels.cs | 11 + TornadoAce_CJOnStyle/Domain/SceneModels.cs | 36 ++ .../Domain/VerificationModels.cs | 10 + TornadoAce_CJOnStyle/Imports.cs | 10 + TornadoAce_CJOnStyle/Package.appxmanifest | 51 ++ .../Properties/PublishProfiles/win-x64.pubxml | 19 + .../Properties/launchSettings.json | 10 + .../Services/AutomationWorkspace.cs | 70 +++ .../Services/IntegrationInterfaces.cs | 21 + .../TornadoAce_CJOnStyle.csproj | 67 +++ .../ViewModels/BaseViewModel.cs | 13 + .../ViewModels/MainViewModel.cs | 202 +++++++ .../ViewModels/ShellModels.cs | 70 +++ TornadoAce_CJOnStyle/Views/MainPage.xaml | 499 ++++++++++++++++++ TornadoAce_CJOnStyle/Views/MainPage.xaml.cs | 17 + TornadoAce_CJOnStyle/app.manifest | 24 + docs/initial-architecture.md | 24 + 28 files changed, 1315 insertions(+) create mode 100644 .gitignore create mode 100644 TornadoAce_CJOnStyle.slnx create mode 100644 TornadoAce_CJOnStyle/App.xaml create mode 100644 TornadoAce_CJOnStyle/App.xaml.cs create mode 100644 TornadoAce_CJOnStyle/Assets/LockScreenLogo.scale-200.png create mode 100644 TornadoAce_CJOnStyle/Assets/SplashScreen.scale-200.png create mode 100644 TornadoAce_CJOnStyle/Assets/Square150x150Logo.scale-200.png create mode 100644 TornadoAce_CJOnStyle/Assets/Square44x44Logo.scale-200.png create mode 100644 TornadoAce_CJOnStyle/Assets/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 TornadoAce_CJOnStyle/Assets/StoreLogo.png create mode 100644 TornadoAce_CJOnStyle/Assets/Wide310x150Logo.scale-200.png create mode 100644 TornadoAce_CJOnStyle/Domain/CueSheetModels.cs create mode 100644 TornadoAce_CJOnStyle/Domain/SceneModels.cs create mode 100644 TornadoAce_CJOnStyle/Domain/VerificationModels.cs create mode 100644 TornadoAce_CJOnStyle/Imports.cs create mode 100644 TornadoAce_CJOnStyle/Package.appxmanifest create mode 100644 TornadoAce_CJOnStyle/Properties/PublishProfiles/win-x64.pubxml create mode 100644 TornadoAce_CJOnStyle/Properties/launchSettings.json create mode 100644 TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs create mode 100644 TornadoAce_CJOnStyle/Services/IntegrationInterfaces.cs create mode 100644 TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj create mode 100644 TornadoAce_CJOnStyle/ViewModels/BaseViewModel.cs create mode 100644 TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs create mode 100644 TornadoAce_CJOnStyle/ViewModels/ShellModels.cs create mode 100644 TornadoAce_CJOnStyle/Views/MainPage.xaml create mode 100644 TornadoAce_CJOnStyle/Views/MainPage.xaml.cs create mode 100644 TornadoAce_CJOnStyle/app.manifest create mode 100644 docs/initial-architecture.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f19a8f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +bin/ +obj/ +.vs/ +.vscode/ + +*.user +*.rsuser +*.suo +*.userosscache +*.sln.docstates + +TestResults/ +*.nupkg +*.snupkg + +publish/ +artifacts/ diff --git a/TornadoAce_CJOnStyle.slnx b/TornadoAce_CJOnStyle.slnx new file mode 100644 index 0000000..deeec63 --- /dev/null +++ b/TornadoAce_CJOnStyle.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/TornadoAce_CJOnStyle/App.xaml b/TornadoAce_CJOnStyle/App.xaml new file mode 100644 index 0000000..4f551da --- /dev/null +++ b/TornadoAce_CJOnStyle/App.xaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TornadoAce_CJOnStyle/App.xaml.cs b/TornadoAce_CJOnStyle/App.xaml.cs new file mode 100644 index 0000000..99b9ab8 --- /dev/null +++ b/TornadoAce_CJOnStyle/App.xaml.cs @@ -0,0 +1,52 @@ +namespace TornadoAce_CJOnStyle +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application + { + private Window? window; + + /// + /// 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(). + /// + public App() + { + this.InitializeComponent(); + } + + /// + /// 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. + /// + /// Details about the launch request and process. + 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(); + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + } +} diff --git a/TornadoAce_CJOnStyle/Assets/LockScreenLogo.scale-200.png b/TornadoAce_CJOnStyle/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..7440f0d4bf7c7e26e4e36328738c68e624ee851e GIT binary patch literal 432 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(FqV6|IEGZ*x-#9g>~Mkr+x6^F zy~CDX2QIMs&Gcs3RnRBoxBA!*(Mfw0KTCYuYk0WlEIV>qBmPl! zq4ukrvfADX@#p8fbLY(H47N+k`FZ(FZh?cDro7>{8mkBO3>^oaIx`3!Jl)Qq)HI!+ z(S=1{o~eT)&W^=Ea8C`-17(Jv5(nHFJ{dOjGdxLVkY_y6&S1whfuFI4MM0kF0f&cO zPDVpV%nz;Id$>+0Ga5e9625-JcI)oq=#Pa3p^>8BB}21BUw@eN!-6@w%X+^`+Vn?! zryu|3T>kVWNBYyBc=7Y6H#s1Ah!OI_nezW zXTqOdkv2Az6KKBV=$yHdF^R3Fqw(TZEoNSZX>reXJ#bwX42%f|Pgg&ebxsLQ010xn AssI20 literal 0 HcmV?d00001 diff --git a/TornadoAce_CJOnStyle/Assets/SplashScreen.scale-200.png b/TornadoAce_CJOnStyle/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..32f486a86792a5e34cd9a8261b394c49b48f86be GIT binary patch literal 5372 zcmd5=Z){Ul6u)iv53sCbIJKLzl(EF%0tzcEY@|pLrfgF~2Dk$KFtU+$kbYqDN5W%7 z>?DBo!@y06eh{Oux>brrNT^{MO(tkiC@nH(2}}G_1|uvcMD(0{?|W^Gxo!tG~hW2Rn&7%b`-Kd_^`BCrb>XVtRKONoEw6%NswzMxk+kbocuk&}kJ#hSP z>8uR{r%LJ?I#)aaWW;uEixz+DzyTpp)MTEo&R%nEA92~g{^eXQwKV1m{xl5K<@k3FacT+Z zrwfy=VocIptI>t%@p5a;Rt=WXVnU;2SUdr7Yk>gw_2z_ICK^23$|Cg7{3Eg5j@N*F zetT?>30(*S_7ld-Yt&u7T{(hEjjM#vPlXibjrq?;pBBx3*>_2~VFGdsH5L zQKme_LAebV}aOX#+rQafZtp+4jK}V!>pn1?+eUH$0%6}z(Kul9!^2z zXi+d@jnx)RW7!j9uFEdv5N&1sCW#Z6Ej5Y7c;o28Q7i%U0(2v5J>o9P zl$#C8&9r)nL;?J65^GIeSOHYr3B7}}R~}@2Tx_xo5*YdU#g1bO}95cq69J!efdlE+xj1qG#ZUqh~1Sn#dBsZfDvcupM zXOFoyJ0$s+RHQKpzr#T>c&EUbq)lGvZDxuI!9unMI=#;ob2&gT)WqOjt6^X`_N21r`&eh6h0xpT!n6Z9rvE&+bFU$vTJO2? z#^tBNOx*2N)~(+TH8d>ep6``8V=3JEfdUUahVZ-xN+k#V&32x|%qnX(XBii5<@`%^ zV#Ky4f1!6RJqJXBU3M4~tmj2;;r`8_j&w?h5g35uMH(QI$Xpesb zG|*XRT?kh6M(jj0Y&vF^M*9g-iDMW%G%9%Pa}6ERQ9b0%6z1v}Ja=|L@G#5ZI>JS9 z*(K12nMvS?oyG8s9|q~{w`ajtI`KSHSiJ;)%X@M&eCE(VqI#F(XL?L@A$TUT?6av5 zkPWIR391XjSC%d6L}7F71Qpw(;c_~)mSZo-&Fm^FHlPX|Fu}1B3E+9j0}o1a(4HFS zUItE22CC%XZi!b4%~vWn>rpV9&CUEvt!?Q{Pr*L~51&(0Sz{VJJFrJtWw2PwXd|J{ zgH%3vAY$flodH=4&ruCHX;(3t;o}n?!0~3EE|5qRz$!VIkphxa4@_jyfiE9m;0 zjcYJ2;26N&MTB8X4joZ&?SUe|VS$^I%dt{!c2O;%3SdqW@K_14r8eyC1s&VcU5+2~ z_O1Cc*w|aIA=VC6AT_EFoL}W#Rl;7CZe)e}RS*e;8CVyM6i8a(yO@|S709VYY(y2g zc+QxB>Bw^B^2Db~*o)=i$m-aUNQFkYy5(eJW$cez>C{POds*p3cy#tHnvActP;dBP zdEf)C;lq}&#PE?XCD<~ngrzYUg|nS`#MS`Rd7cT>xlR19P#~4Qg5!J}@glCUq)z_2 zjvyv%aSq0 z)njao1dV0XNw&c@qmj1e*jgQ$l@_urW5G4RSY#rT1z`#%3;{EB`aJK|TH^lb_3nAT z-_Q4X-(K&IS8UyqsnjYdippfmN-HT!X2MT;Dpcy~-#$k6V z|MR4vU#O&p7TC46pTflb3 zoUJ;ZRf#&8&EwXy5s%!&(q6cN62swD#FH%O-RJsjWPZN3^^@FCIQ&MxXIFo7!I#VI zkpIstuWqUV5uhgs07?k$*!`uiZ=5b#$lI|0c+XJvj(}zSE3MN#EyOK zql(#yA}~Ibl*r(s1}Z^5mmn*-n93g?-ccM+^PN?6HH~h0hjy6@XY*^i<-V)+OZ;p7 z7j`p_sT55xnYsedNIIel^QIIg7i@`2Qi}x5$!tk29$2OQI zs^kQXAKE}5ZJu$)2@Dxn?}}O@f@6@^!%9Tj+o>=jd!^ZuvBE4jb4g}Z5WMBtcmy^~ zoFGVS5|0FA!(1Q%fL?Bj*L+9ZL{mjSO8lzqrQ0UCZ)X zPwk$1HNFgaK%NxGpuXz}#ywXvf2JQ?BQ5uOZM2up4S#ieaxS$!o9o6Z=czNQb} zwAh|xLZ>+WyN%o?^uCAQw&&4o?S$DJ`WP(Hr*grL*qNXlqU0osCQ(Up5F(^$Z5;n&oJIO4uF`k&QL*j{f zU=;#MZ5{@b%qMbjTB3dh-5#mqY>%{0jgS+WdHyG literal 0 HcmV?d00001 diff --git a/TornadoAce_CJOnStyle/Assets/Square44x44Logo.scale-200.png b/TornadoAce_CJOnStyle/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..f713bba67f551ef91020b75716a4dc8ebd744b1c GIT binary patch literal 637 zcmeAS@N?(olHy`uVBq!ia0vp^5g^RL1|$oo8kjIJFu8cTIEGZ*dUI*J;2{SImxtDO zm%3!R$UazoY}x{$j0P5ABYXWr(l=jxJ6ps1W{tV=^>{Dl><3nv3A}sm=EZ)#l3`NR zpZda3^rNox*D1%NC98Z~L*6zipLw~Gxn&(Y-;KmJ+aR6eLabU-L#y8HW%7P-E_-VlLqIabbHPHKT*)fT@9iWJ7iWgOT9%0}Lrj>lztPxWq6sPw3pi z#-<=#$jjrP_DD*i!RLsn0mIA=>4~N)IMYWIf=j%-zuKCdMG%tHYot70D1| zvWa0wMhauW#S>1CnI_;>!1Q3zMA17@DOVq{MQ+{U7^a&yA+%dMCG;WNPV0i;w$tu; zX^b}UKziPM)(<;)ruW;-`)bBN+rQNM*Zs_>?n$|FVFo-e*PZb*@U7VAd+tHb4e?=Blc~}S6K)wL}r*Gf`BM#QB z+y>N$mCswb4d{^{S9v_!eQj4fTRMOwOCi?lSk9%<=vAz}jM-*PQtH@Odn1LZcd^j#o> hW$4xn+CT+ep9lJ{OAO?njobhL002ovPDHLkV1nYebbkN< literal 0 HcmV?d00001 diff --git a/TornadoAce_CJOnStyle/Assets/StoreLogo.png b/TornadoAce_CJOnStyle/Assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..a4586f26bdf7841cad10f39cdffe2aca3af252c1 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2o;fF!p=8IEGZ*dUM0H=rDtTTVkd2 z(%lbKn@VS_lUaADVB&;Z6F#LM+mPsa?e>FnHo;HND^!P`-lX%BH~FOg%y&x+t*x!? zg$#_1A1kgsSvO(fw`bOmo;lrJX8byO1j^gf7qohR%mmt z@L)WX;>gqgK|tWJvQ5j;4;=gt4HXVKSMYRv5RhY5vS~TqfK_NAP*r{h!!g^BZ;w4r z7CGdsai)y;fJQc`7{Zc2b==h%o`Op$|bg6a&nL{*m7-=0>k4M4-PXlU;G-?%*(*g>iFt^ U$m#7DfHB12>FVdQ&MBb@0G`#n8vpc0sq%A~kJcD9FY~qQRMt?ZR3YyDZt}Od;|mgpc{2dv9AHF){kXU%k({ z=Y8JidEayHTkG@twPZ|U3_^%3ct-OgLSiFAqDN!|tbCX@c@?4P`2x*TMK!+Q4b?k0 ziW7!!KF6dPWcF<%I|iznM~`QJ_V7sHGV_D`dhgpA9Vd@&X}ErK+j~_rdv;Bp?OA@a zFXOk7eWOJe5NcK;6h$FaM&7JxNc#-@QTwzW6x#d_zmQNkz5) zPI;kh;3d;5UCJU+9a(cOxX(|edWoOiAEdGU#kPJ&xnc2||3vDbuhBCkj-pb0as$Zl z5;}4n=**n6(1g`JEtSy;SG6X;#-F~Oz3lESG2b5`j@wAwY4Yp<=4Xeb>iH=6aicF?DxD&q{`!&}ct zBI)aycwuobQAf&678Uf+Mmh-@9RUhyH~>?w0dixO0#jZjEc9R^=5NZw=|a(kcB?9^ zfnTiEFXp-q#B;Tn>(O%$A*ud^Rg&eVH6Y_5Y%!E39RR&s?XpG`gKwU!6FE1 z7X)DC7)*(5g}lh`4`{i~DZcWupZI`K)_4P)VE{@gc7@Xsd^86zl~_mOYH?I4!aGeX z^E(_=L6?PgveDQ+r%P@UISEXrkn`LHJZ##+!-anV>6h)IkKp;E@p8+3&(5%kS2)ld*J*rJccZM0iyaAx7+F~GW1UWFK&3X$PE1^}NH zgAG9ck5K!{07OwU@j@Do>TbH=CDEo#4m0cEyAuXy_<&jlzJVcKweSJ5 z&=q~iIn18$w8yb=rmEmHxVEUA^?RwnB?6Qlp1os8@*dWTGL2bhzZ!s*xqScR?EPL` zo(JwNdKUUYy7GtvZ3asXm)cgFvCx9EmAi;|w=a0iGiv%%VYKh`P0Wma4y`Xyx|T~( zAmfGbgbEEC7)j8b@WA@+5W3a61HJXC1dX@6_T|Czk0I0zBk%tnW~()VWITGI!`$c< gARL?UBrYYkwoDw4eo*CrzXGTrZ@;GF>596)00d&n@&Et; literal 0 HcmV?d00001 diff --git a/TornadoAce_CJOnStyle/Domain/CueSheetModels.cs b/TornadoAce_CJOnStyle/Domain/CueSheetModels.cs new file mode 100644 index 0000000..1b7ce3f --- /dev/null +++ b/TornadoAce_CJOnStyle/Domain/CueSheetModels.cs @@ -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); +} diff --git a/TornadoAce_CJOnStyle/Domain/SceneModels.cs b/TornadoAce_CJOnStyle/Domain/SceneModels.cs new file mode 100644 index 0000000..2bbae93 --- /dev/null +++ b/TornadoAce_CJOnStyle/Domain/SceneModels.cs @@ -0,0 +1,36 @@ +using System.Text.Json; + +namespace TornadoAce_CJOnStyle.Domain +{ + public sealed record SceneTemplateDefinition( + string TemplateId, + string TemplateName, + string TornadoVersion, + IReadOnlyList Mappings); + + public sealed record FieldMappingDefinition( + string CueSheetField, + string SceneObjectName, + string Attribute, + string Rule); + + public sealed record SceneDataSnapshot( + string SceneName, + string TemplateId, + IReadOnlyDictionary VisibleTextObjects, + IReadOnlyList ImagePaths) + { + public string ToPreviewJson() + { + var payload = new + { + scene = SceneName, + templateId = TemplateId, + text = VisibleTextObjects, + images = ImagePaths + }; + + return JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }); + } + } +} diff --git a/TornadoAce_CJOnStyle/Domain/VerificationModels.cs b/TornadoAce_CJOnStyle/Domain/VerificationModels.cs new file mode 100644 index 0000000..357d7a7 --- /dev/null +++ b/TornadoAce_CJOnStyle/Domain/VerificationModels.cs @@ -0,0 +1,10 @@ +namespace TornadoAce_CJOnStyle.Domain +{ + public sealed record InspectionResult( + string CueSheetId, + string ProgramTitle, + string SceneName, + double MatchRate, + string Severity, + string Decision); +} diff --git a/TornadoAce_CJOnStyle/Imports.cs b/TornadoAce_CJOnStyle/Imports.cs new file mode 100644 index 0000000..02d180e --- /dev/null +++ b/TornadoAce_CJOnStyle/Imports.cs @@ -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; diff --git a/TornadoAce_CJOnStyle/Package.appxmanifest b/TornadoAce_CJOnStyle/Package.appxmanifest new file mode 100644 index 0000000..a2276a6 --- /dev/null +++ b/TornadoAce_CJOnStyle/Package.appxmanifest @@ -0,0 +1,51 @@ + + + + + + + + + + TornadoAce CJ OnStyle + Comtropy + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TornadoAce_CJOnStyle/Properties/PublishProfiles/win-x64.pubxml b/TornadoAce_CJOnStyle/Properties/PublishProfiles/win-x64.pubxml new file mode 100644 index 0000000..eea93de --- /dev/null +++ b/TornadoAce_CJOnStyle/Properties/PublishProfiles/win-x64.pubxml @@ -0,0 +1,19 @@ + + + + + FileSystem + x64 + win-x64 + win10-x64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + False + True + + \ No newline at end of file diff --git a/TornadoAce_CJOnStyle/Properties/launchSettings.json b/TornadoAce_CJOnStyle/Properties/launchSettings.json new file mode 100644 index 0000000..933a7f2 --- /dev/null +++ b/TornadoAce_CJOnStyle/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "TornadoAce_CJOnStyle (Package)": { + "commandName": "MsixPackage" + }, + "TornadoAce_CJOnStyle (Unpackaged)": { + "commandName": "Project" + } + } +} diff --git a/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs b/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs new file mode 100644 index 0000000..c3b51e8 --- /dev/null +++ b/TornadoAce_CJOnStyle/Services/AutomationWorkspace.cs @@ -0,0 +1,70 @@ +using TornadoAce_CJOnStyle.Domain; + +namespace TornadoAce_CJOnStyle.Services +{ + public sealed class AutomationWorkspace + { + private AutomationWorkspace( + IReadOnlyList cueSheets, + SceneTemplateDefinition activeTemplate, + SceneDataSnapshot activeScene, + IReadOnlyList inspectionResults) + { + CueSheets = cueSheets; + ActiveTemplate = activeTemplate; + ActiveScene = activeScene; + InspectionResults = inspectionResults; + } + + public IReadOnlyList CueSheets { get; } + + public SceneTemplateDefinition ActiveTemplate { get; } + + public SceneDataSnapshot ActiveScene { get; } + + public IReadOnlyList 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 + { + ["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); + } + } +} diff --git a/TornadoAce_CJOnStyle/Services/IntegrationInterfaces.cs b/TornadoAce_CJOnStyle/Services/IntegrationInterfaces.cs new file mode 100644 index 0000000..bad40d4 --- /dev/null +++ b/TornadoAce_CJOnStyle/Services/IntegrationInterfaces.cs @@ -0,0 +1,21 @@ +using TornadoAce_CJOnStyle.Domain; + +namespace TornadoAce_CJOnStyle.Services +{ + public interface ICueSheetClient + { + Task> GetCueSheetsAsync(DateOnly broadcastDate, CancellationToken cancellationToken); + } + + public interface ITornadoSceneAdapter + { + Task CreateOrUpdateSceneAsync(CueSheetRecord cueSheet, SceneTemplateDefinition template, CancellationToken cancellationToken); + + Task ExtractSceneDataAsync(string scenePath, CancellationToken cancellationToken); + } + + public interface IInspectionService + { + Task InspectAsync(CueSheetRecord cueSheet, SceneDataSnapshot sceneSnapshot, CancellationToken cancellationToken); + } +} diff --git a/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj b/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj new file mode 100644 index 0000000..fd11a15 --- /dev/null +++ b/TornadoAce_CJOnStyle/TornadoAce_CJOnStyle.csproj @@ -0,0 +1,67 @@ + + + + net8.0-windows10.0.19041.0 + 10.0.17763.0 + WinExe + + + true + true + None + + + + enable + latest + enable + TornadoAce_CJOnStyle + + + true + x64 + app.manifest + win-$(Platform).pubxml + win-x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + diff --git a/TornadoAce_CJOnStyle/ViewModels/BaseViewModel.cs b/TornadoAce_CJOnStyle/ViewModels/BaseViewModel.cs new file mode 100644 index 0000000..4216568 --- /dev/null +++ b/TornadoAce_CJOnStyle/ViewModels/BaseViewModel.cs @@ -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); + } + } +} diff --git a/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs new file mode 100644 index 0000000..2cd6f10 --- /dev/null +++ b/TornadoAce_CJOnStyle/ViewModels/MainViewModel.cs @@ -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 StatusCards { get; } = []; + + public ObservableCollection PipelineSteps { get; } = []; + + public ObservableCollection FieldMappings { get; } = []; + + public ObservableCollection Endpoints { get; } = []; + + public ObservableCollection 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)); + } + } +} diff --git a/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs b/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs new file mode 100644 index 0000000..34199cb --- /dev/null +++ b/TornadoAce_CJOnStyle/ViewModels/ShellModels.cs @@ -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); +} diff --git a/TornadoAce_CJOnStyle/Views/MainPage.xaml b/TornadoAce_CJOnStyle/Views/MainPage.xaml new file mode 100644 index 0000000..4d6ae47 --- /dev/null +++ b/TornadoAce_CJOnStyle/Views/MainPage.xaml @@ -0,0 +1,499 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TornadoAce_CJOnStyle/Views/MainPage.xaml.cs b/TornadoAce_CJOnStyle/Views/MainPage.xaml.cs new file mode 100644 index 0000000..d5de1a4 --- /dev/null +++ b/TornadoAce_CJOnStyle/Views/MainPage.xaml.cs @@ -0,0 +1,17 @@ +namespace TornadoAce_CJOnStyle.Views +{ + /// + /// A simple page that can be used on its own or navigated to within a Frame. + /// + public partial class MainPage : Page + { + private readonly MainViewModel viewModel = new(); + + public MainPage() + { + this.InitializeComponent(); + } + + public MainViewModel ViewModel => viewModel; + } +} diff --git a/TornadoAce_CJOnStyle/app.manifest b/TornadoAce_CJOnStyle/app.manifest new file mode 100644 index 0000000..b6202b6 --- /dev/null +++ b/TornadoAce_CJOnStyle/app.manifest @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + PerMonitorV2 + + + diff --git a/docs/initial-architecture.md b/docs/initial-architecture.md new file mode 100644 index 0000000..4c5677e --- /dev/null +++ b/docs/initial-architecture.md @@ -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이 반환할 중요도 스키마와 감사 로그 보관 범위.