@sigit / siti / commits / ffc785e

wip

paydii committed Jul 25, 2026 at 10:21 UTC ffc785e593cd9bab821591913619bb7a4ceb4404
12 files changed +228 -4
assets/aso/microsoftstore/ar.md
+1 -1
@@ -109,7 +109,7 @@
109
110 تتضمن هذه الإصدارة تحسينات في الأداء وإصلاحات للأخطاء.
111
112 -## Search Terms (7 terms, max 30 characters each)
112 +## Search Terms (7 terms, max 40 characters each, max 21 words total)
113 1. مساعد ذكاء اصطناعي
114 2. ذكاء اصطناعي خاص
115 3. محادثة ذكاء بدون انترنت
assets/aso/microsoftstore/en-US.md
+1 -1
@@ -109,7 +109,7 @@ Built with modern native technology for a fast, lightweight experience on your W
109
110 Performance improvements and bug fixes included in this release.
111
112 -## Search Terms (7 terms, max 30 characters each)
112 +## Search Terms (7 terms, max 40 characters each, max 21 words total)
113 1. AI assistant
114 2. private AI
115 3. offline AI chat
assets/aso/microsoftstore/id.md
+1 -1
@@ -109,7 +109,7 @@ Dibuat dengan teknologi natif modern untuk pengalaman cepat dan ringan di PC Win
109
110 Peningkatan performa dan perbaikan bug disertakan dalam rilis ini.
111
112 -## Search Terms (7 terms, max 30 characters each)
112 +## Search Terms (7 terms, max 40 characters each, max 21 words total)
113 1. asisten AI
114 2. AI privat
115 3. obrolan AI offline
assets/aso/microsoftstore/screenshots/generate.mjs new
+224
@@ -0,0 +1,224 @@
1 +// Microsoft Store screenshot generator — Siti AI.
2 +// Renders editorial frames using Siti AI's REAL chat UI markup and its own
3 +// stylesheet (src/App.css), framed with a caption in the app's minimal,
4 +// monochrome Scandinavian style, at the Windows Store desktop size
5 +// (1920x1080 landscape) via headless chromium.
6 +//
7 +// node assets/aso/microsoftstore/screenshots/generate.mjs (run from repo root)
8 +//
9 +// Output: assets/aso/microsoftstore/screenshots/windows/NN-<slug>.png (1920x1080)
10 +// Playwright is borrowed from the sibling SplitFire repo's shared cache
11 +// (playwright is repo-agnostic); Siti AI has none of its own.
12 +//
13 +// Honest feature set only (see assets/aso/microsoftstore/en-US.md): a private,
14 +// on-device AI assistant — ask anything, draft, summarize, translate; no
15 +// cloud, no account, works offline after a one-time model download.
16 +
17 +import { createRequire } from "node:module";
18 +import { existsSync, mkdirSync, readFileSync } from "node:fs";
19 +import { dirname, resolve } from "node:path";
20 +import { fileURLToPath } from "node:url";
21 +
22 +const HERE = dirname(fileURLToPath(import.meta.url));
23 +const REPO = resolve(HERE, "../../../..");
24 +const require = createRequire(import.meta.url);
25 +const pw = [
26 + process.env.HOME + "/Repositories/splitfire/.ds-sync/node_modules/playwright",
27 + resolve(REPO, ".ds-sync/node_modules/playwright"),
28 +].find((p) => existsSync(p));
29 +if (!pw) throw new Error("playwright not found (borrow from splitfire/.ds-sync)");
30 +const { chromium } = require(pw);
31 +
32 +// The app's real stylesheet is the source of truth for the chat UI look.
33 +const APP_CSS = readFileSync(resolve(REPO, "src/App.css"), "utf8");
34 +
35 +const PLATFORMS = { windows: { w: 1920, h: 1080, zoom: 1.15, cap: 62, sub: 25 } };
36 +
37 +// ── Real Siti AI markup (classes from src/App.tsx + src/App.css) ────────────
38 +const bubble = (role, text) =>
39 + `<div class="message-row ${role}"><div class="bubble">${text}</div></div>`;
40 +
41 +const settingsIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`;
42 +const sendIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 2 11 13"/><path d="M22 2 15 22l-4-9-9-4 20-7z"/></svg>`;
43 +
44 +// A bounded chat window holding the real .app markup.
45 +const chatWindow = (subtitle, rows, placeholder = "Message Siti…") => `
46 + <div class="siti-window">
47 + <div class="app">
48 + <header class="header">
49 + <div class="header-title">
50 + <h1>Siti AI</h1>
51 + <span class="header-subtitle">${subtitle}</span>
52 + </div>
53 + <div class="header-actions">
54 + <span class="status-dot"></span>
55 + <button class="icon-button">${settingsIcon}</button>
56 + </div>
57 + </header>
58 + <div class="messages">${rows}</div>
59 + <div class="input-area">
60 + <div class="input-form">
61 + <input class="input-field" value="" placeholder="${placeholder}" readonly />
62 + <button class="send-button">${sendIcon}</button>
63 + </div>
64 + <div class="input-hint">Private &middot; runs on your PC</div>
65 + </div>
66 + </div>
67 + </div>`;
68 +
69 +const settingsWindow = () => `
70 + <div class="siti-window">
71 + <div class="app">
72 + <div class="settings-header">
73 + <h1>Settings</h1>
74 + <button class="settings-done">Done</button>
75 + </div>
76 + <div class="settings-content">
77 + <div class="settings-section">
78 + <div class="section-label">Model</div>
79 + <div class="settings-card">
80 + <div class="settings-row">
81 + <span class="row-label">On-device model</span>
82 + <span class="row-value">Gemma 2 &middot; 2B</span>
83 + </div>
84 + <div class="model-detail">
85 + <div class="model-org">Google DeepMind</div>
86 + <div class="model-desc">Runs entirely on your PC. Downloaded once, then works offline.</div>
87 + <div class="model-status ready"><span class="model-status-dot"></span>Ready</div>
88 + </div>
89 + </div>
90 + <div class="settings-note">No account, no cloud, no tracking. Your conversations never leave this device.</div>
91 + </div>
92 + </div>
93 + </div>
94 + </div>`;
95 +
96 +// ── Frames (honest: private, on-device, capable, offline) ───────────────────
97 +const FRAMES = [
98 + {
99 + slug: "ask-siti-anything",
100 + caption: "Ask Siti anything",
101 + sub: "A capable assistant for questions, drafts, and ideas",
102 + stage: () =>
103 + chatWindow("Gemma 2 &middot; 2B &middot; private", [
104 + bubble("user", "Explain vector embeddings like I'm five."),
105 + bubble(
106 + "assistant",
107 + "Imagine every word gets a spot on a huge map. Words that mean similar things sit close together — so the computer can tell that “cat” and “kitten” are neighbours.",
108 + ),
109 + bubble("user", "Nice. Now give me three names for a coffee app."),
110 + bubble("assistant", "Sure — Dailygrind, Pour, and Warmup. Want a few more playful ones?"),
111 + ].join("")),
112 + },
113 + {
114 + slug: "private-on-device",
115 + caption: "Private by design",
116 + sub: "Every conversation stays on your own machine",
117 + stage: () =>
118 + chatWindow("Gemma 2 &middot; 2B &middot; private", [
119 + bubble("user", "Is any of this sent to the cloud?"),
120 + bubble(
121 + "assistant",
122 + "No. Everything runs on your PC. Your messages never leave this device — no account, no servers, no tracking.",
123 + ),
124 + bubble("user", "So I can paste something sensitive?"),
125 + bubble("assistant", "Yes. Nothing you type here is uploaded. It is yours alone."),
126 + ].join("")),
127 + },
128 + {
129 + slug: "draft-and-summarize",
130 + caption: "Draft, summarise, translate",
131 + sub: "Turn a rough note into something finished",
132 + stage: () =>
133 + chatWindow("Gemma 2 &middot; 2B &middot; private", [
134 + bubble("user", "Summarise this in one line: the team agreed to ship the beta on Friday, Alex owns QA, Sam writes the release notes."),
135 + bubble("assistant", "Beta ships Friday — Alex on QA, Sam on release notes."),
136 + bubble("user", "Now say it in Indonesian."),
137 + bubble("assistant", "Beta rilis Jumat — Alex urus QA, Sam tulis catatan rilis."),
138 + ].join("")),
139 + },
140 + {
141 + slug: "works-offline",
142 + caption: "Works offline, anywhere",
143 + sub: "After a one-time download, no connection needed",
144 + stage: () =>
145 + chatWindow("Gemma 2 &middot; 2B &middot; offline", [
146 + bubble("user", "Do I need internet for this?"),
147 + bubble(
148 + "assistant",
149 + "Nope. Once the model is downloaded, Siti works fully offline — on a plane, on a commute, or off the grid, it still answers.",
150 + ),
151 + bubble("user", "Perfect for the flight tomorrow."),
152 + bubble("assistant", "Exactly. Airplane mode, no problem."),
153 + ].join("")),
154 + },
155 + {
156 + slug: "on-device-model",
157 + caption: "The model lives on your PC",
158 + sub: "One download, then it is yours — no sign-in, no cloud",
159 + stage: () => settingsWindow(),
160 + },
161 +];
162 +
163 +// ── Page ────────────────────────────────────────────────────────────────────
164 +const WIN_W = 560;
165 +const WIN_H = 672;
166 +
167 +const page = (p, frame, idx) => `<!doctype html><html><head><meta charset="utf-8"><style>
168 + ${APP_CSS}
169 + /* Force the light theme regardless of the headless colour-scheme. */
170 + :root{--bg:#fafafa;--surface:#ffffff;--border:#e8e8e8;--text-primary:#1a1a1a;--text-secondary:#8a8a8a;--accent:#2d2d2d;--accent-hover:#1a1a1a;--user-bubble:#1a1a1a;--user-bubble-text:#fff;--ai-bubble:#f0f0f0;--ai-bubble-text:#1a1a1a;--input-bg:#f5f5f5}
171 + html,body{margin:0;width:${p.w}px;height:${p.h}px;overflow:hidden}
172 + .shot{width:${p.w}px;height:${p.h}px;display:flex;flex-direction:column;
173 + font-family:"Inter",-apple-system,"Segoe UI",Roboto,sans-serif;color:#1a1a1a;
174 + background:
175 + radial-gradient(1100px 640px at 82% 14%, rgba(0,0,0,0.035), transparent 55%),
176 + linear-gradient(165deg,#f7f7f5 0%,#eeeeeb 100%);
177 + background-color:#f2f2ef}
178 + .cap-wrap{padding:${Math.round(p.h * 0.055)}px ${Math.round(p.w * 0.07)}px 0}
179 + .cap-index{display:inline-block;background:#1a1a1a;color:#fafafa;padding:5px 11px;border-radius:6px;
180 + font-size:${Math.round(p.sub * 0.72)}px;font-weight:700;letter-spacing:0.16em;text-transform:uppercase}
181 + .cap{font-size:${p.cap}px;line-height:1.04;font-weight:700;letter-spacing:-0.03em;margin-top:20px;color:#141414}
182 + .sub{font-size:${p.sub}px;margin-top:15px;color:#6a6a6a}
183 + .stage{flex:1;display:flex;align-items:center;justify-content:center;padding:${Math.round(p.h * 0.035)}px}
184 + .stage-inner{zoom:${p.zoom}}
185 + /* Bounded desktop window that holds the real .app markup. */
186 + .siti-window{width:${WIN_W}px;height:${WIN_H}px;background:var(--bg);border:1px solid var(--border);
187 + border-radius:22px;overflow:hidden;box-shadow:0 34px 80px rgba(20,20,20,0.16)}
188 + .siti-window .app{height:100%}
189 + .siti-window .header,.siti-window .input-area{padding-left:22px;padding-right:22px}
190 + .siti-window .header{padding-top:20px;padding-bottom:16px}
191 + .siti-window .input-area{padding-top:16px;padding-bottom:18px}
192 + .siti-window .settings-header{padding:20px}
193 + .siti-window .messages{padding:20px 0}
194 + </style></head>
195 + <body>
196 + <div class="shot">
197 + <div class="cap-wrap">
198 + <span class="cap-index">Siti AI &mdash; 0${idx + 1}</span>
199 + <div class="cap">${frame.caption}</div>
200 + <div class="sub">${frame.sub}</div>
201 + </div>
202 + <div class="stage"><div class="stage-inner">${frame.stage(p)}</div></div>
203 + </div>
204 + </body></html>`;
205 +
206 +// ── Render ──────────────────────────────────────────────────────────────────
207 +const browser = await chromium.launch();
208 +try {
209 + for (const [key, p] of Object.entries(PLATFORMS)) {
210 + const dir = resolve(HERE, key);
211 + mkdirSync(dir, { recursive: true });
212 + const ctx = await browser.newContext({ viewport: { width: p.w, height: p.h }, deviceScaleFactor: 1 });
213 + const pg = await ctx.newPage();
214 + for (const [i, frame] of FRAMES.entries()) {
215 + await pg.setContent(page(p, frame, i), { waitUntil: "load" });
216 + const file = resolve(dir, `0${i + 1}-${frame.slug}.png`);
217 + await pg.screenshot({ path: file, clip: { x: 0, y: 0, width: p.w, height: p.h } });
218 + console.log("wrote", file.replace(REPO + "/", ""));
219 + }
220 + await ctx.close();
221 + }
222 +} finally {
223 + await browser.close();
224 +}
assets/aso/microsoftstore/screenshots/windows/01-ask-siti-anything.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/01-ask-siti-anything.png differ
assets/aso/microsoftstore/screenshots/windows/02-private-on-device.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/02-private-on-device.png differ
assets/aso/microsoftstore/screenshots/windows/03-draft-and-summarize.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/03-draft-and-summarize.png differ
assets/aso/microsoftstore/screenshots/windows/04-works-offline.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/04-works-offline.png differ
assets/aso/microsoftstore/screenshots/windows/05-on-device-model.png
Binary files /dev/null and b/assets/aso/microsoftstore/screenshots/windows/05-on-device-model.png differ
assets/aso/microsoftstore/store-listing/app-tile-icon-300.png
Binary files /dev/null and b/assets/aso/microsoftstore/store-listing/app-tile-icon-300.png differ
assets/aso/microsoftstore/store-listing/hero-art-1920x1080.png
Binary files /dev/null and b/assets/aso/microsoftstore/store-listing/hero-art-1920x1080.png differ
assets/aso/microsoftstore/sv-SE.md
+1 -1
@@ -109,7 +109,7 @@ Byggd med modern nativ teknik för en snabb och lätt upplevelse på din Windows
109
110 Prestandaförbättringar och buggfixar ingår i den här versionen.
111
112 -## Search Terms (7 terms, max 30 characters each)
112 +## Search Terms (7 terms, max 40 characters each, max 21 words total)
113 1. AI-assistent
114 2. privat AI
115 3. offline AI-chatt