Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6d8dceb
feat(signage-manager): create and edit signage artwork with AI
Creeves Aug 27, 2026
62dbfad
fix(signage-manager): brand kit, layer layout and the save race
Creeves Aug 27, 2026
84bf0a5
feat(mocks): signage AI endpoints
Creeves Aug 27, 2026
3cbfdac
fix(signage-manager): do not stack AI dialogs
Creeves Aug 27, 2026
4cf6418
feat(signage-manager): name the engine behind the images
Creeves Aug 27, 2026
13fdb92
feat(signage-manager): change an existing image with AI
Creeves Aug 27, 2026
43eb574
feat(signage-manager): show the image being changed
Creeves Aug 28, 2026
e327091
fix(signage-manager): drop the creation-only options from an edit
Creeves Aug 28, 2026
a92c3a9
fix(signage-manager): drop the shape selector from an edit
Creeves Aug 28, 2026
7103fb4
feat(signage-manager): as many blocks of text as the poster needs
Creeves Aug 28, 2026
f29a5fb
feat(signage-manager): let a logo be added, since nothing stores one
Creeves Aug 28, 2026
fe0490e
feat(signage-manager): a branding page for the organisation
Creeves Aug 28, 2026
1e9a09c
feat(signage-manager): full screen AI modal, preview left, settings r…
camreeves Aug 28, 2026
43a934c
feat(signage-manager): a light and a dark logo, and words you drag
camreeves Aug 28, 2026
ac8c0b0
feat(signage-manager): paragraphs, and any colour or face per block
camreeves Aug 28, 2026
9d37a70
fix(signage-manager): one gutter around the playlists panel
camreeves Aug 28, 2026
b613868
fix(signage-manager): one height across the media toolbar
camreeves Aug 28, 2026
95beb58
feat(signage-manager): attach images to a request and name them in th…
camreeves Aug 28, 2026
6ff89da
feat(signage-manager): a switch for whether the poster wears the bran…
camreeves Aug 28, 2026
8b2fbd5
feat(signage-manager): branding is read only unless you are a sys admin
camreeves Aug 31, 2026
e6d2418
fix(signage-manager): the frontend defects found in the pre-push audit
camreeves Aug 31, 2026
c61f0d3
fix(signage-manager): let Tab out of the canvas, and keep the key acr…
camreeves Aug 31, 2026
ffb90f0
fix(signage-manager): harden AI image jobs
MrYuion Aug 31, 2026
70dc120
fix(signage-manager): restore recent AI jobs
MrYuion Aug 31, 2026
dad5c6e
fix(signage-manager): enforce AI action capabilities
MrYuion Aug 31, 2026
69ae14d
refactor(signage-manager): clean up AI support code
MrYuion Aug 31, 2026
304f770
docs: drop the justification paragraphs from the comments
camreeves Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,012 changes: 1,012 additions & 0 deletions apps/signage-manager/src/app/ai/ai-image-modal.component.ts

Large diffs are not rendered by default.

462 changes: 462 additions & 0 deletions apps/signage-manager/src/app/ai/ai-image.service.ts

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions apps/signage-manager/src/app/ai/ai-image.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/** Return the API's string error without leaking response objects to the UI. */
export function errorMessage(error: unknown, fallback: string): string {
if (typeof error === 'string') return error;
if (!isRecord(error)) return fallback;

const nested = error['error'];
if (typeof nested === 'string') return nested;
if (isRecord(nested)) {
const detail = nested['error'];
if (typeof detail === 'string') return detail;
const nested_message = nested['message'];
if (typeof nested_message === 'string') return nested_message;
}

const message = error['message'];
return typeof message === 'string' ? message : fallback;
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}

/** Return an HTTP status from either the error or its wrapped API response. */
export function errorStatus(error: unknown): number | undefined {
if (!isRecord(error)) return undefined;
const status = error['status'];
if (typeof status === 'number') return status;
const nested = error['error'];
if (!isRecord(nested)) return undefined;
const nested_status = nested['status'];
return typeof nested_status === 'number' ? nested_status : undefined;
}

/** Perceived sRGB brightness on a 0 to 255 scale. */
export function perceivedLightness(
red: number,
green: number,
blue: number,
): number {
return (red * 299 + green * 587 + blue * 114) / 1000;
}
417 changes: 417 additions & 0 deletions apps/signage-manager/src/app/ai/ai-layer-controls.component.ts

Large diffs are not rendered by default.

Loading
Loading