feat: project cover art with AI generation, branding rename, RBAC fix, computation graph
- Add DALL-E cover art generation for projects (Azure OpenAI + standard OpenAI)
- CoverArtSection component with generate/upload/remove/focus-point controls
- Client-side image compression (10MB input → WebP/JPEG, max 1920px)
- DALL-E settings in admin panel (deployment, endpoint, API key)
- MCP assistant tools for cover art (generate_project_cover, remove_project_cover)
- Rename "Planarchy" → "plANARCHY" across all UI-facing text (13 files)
- Fix hardcoded canEdit={true} on project detail page — now checks user role
- Computation graph visualization (2D/3D) for calculation rules
- OG image and OpenGraph metadata
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
|
||||
const INPUT_CLASS = "app-input";
|
||||
@@ -90,6 +91,11 @@ export function SystemSettingsClient() {
|
||||
const [scoreSaved, setScoreSaved] = useState(false);
|
||||
const [recomputeResult, setRecomputeResult] = useState<{ updated: number } | null>(null);
|
||||
|
||||
// DALL-E settings
|
||||
const [dalleDeployment, setDalleDeployment] = useState("");
|
||||
const [dalleEndpoint, setDalleEndpoint] = useState("");
|
||||
const [dalleApiKey, setDalleApiKey] = useState("");
|
||||
|
||||
// SMTP settings
|
||||
const [smtpHost, setSmtpHost] = useState("");
|
||||
const [smtpPort, setSmtpPort] = useState(587);
|
||||
@@ -131,6 +137,9 @@ export function SystemSettingsClient() {
|
||||
if (settings.scoreVisibleRoles) {
|
||||
setScoreVisibleRoles(settings.scoreVisibleRoles as SystemRole[]);
|
||||
}
|
||||
// DALL-E
|
||||
setDalleDeployment(settings.azureDalleDeployment ?? "");
|
||||
setDalleEndpoint(settings.azureDalleEndpoint ?? "");
|
||||
// SMTP
|
||||
setSmtpHost(settings.smtpHost ?? "");
|
||||
setSmtpPort(settings.smtpPort ?? 587);
|
||||
@@ -269,6 +278,9 @@ export function SystemSettingsClient() {
|
||||
aiTemperature: temperature,
|
||||
aiSummaryPrompt: summaryPrompt || undefined,
|
||||
...(apiKey ? { azureOpenAiApiKey: apiKey } : {}),
|
||||
azureDalleDeployment: dalleDeployment,
|
||||
azureDalleEndpoint: provider === "azure" && dalleEndpoint ? dalleEndpoint : undefined,
|
||||
...(dalleApiKey ? { azureDalleApiKey: dalleApiKey } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,8 +305,8 @@ export function SystemSettingsClient() {
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
||||
<div className={PANEL_CLASS}>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-[0.18em] text-gray-700 dark:text-gray-200">
|
||||
AI Provider
|
||||
<h2 className="text-sm font-semibold uppercase tracking-[0.18em] text-gray-700 dark:text-gray-200 flex items-center">
|
||||
AI Provider <InfoTooltip content="Configure the AI service used for generating resource skill profile summaries. Either OpenAI directly or Azure OpenAI Service." />
|
||||
</h2>
|
||||
|
||||
{/* Provider toggle */}
|
||||
@@ -522,8 +534,8 @@ export function SystemSettingsClient() {
|
||||
|
||||
{/* Generation settings */}
|
||||
<div className={PANEL_CLASS}>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-[0.18em] text-gray-700 dark:text-gray-200">
|
||||
Generation Settings
|
||||
<h2 className="text-sm font-semibold uppercase tracking-[0.18em] text-gray-700 dark:text-gray-200 flex items-center">
|
||||
Generation Settings <InfoTooltip content="Fine-tune how the AI generates skill profile summaries. These settings affect output length, creativity, and the prompt template." />
|
||||
</h2>
|
||||
|
||||
{/* Max completion tokens */}
|
||||
@@ -989,11 +1001,76 @@ export function SystemSettingsClient() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── DALL-E Image Generation ────────────────────────────────── */}
|
||||
<div className={PANEL_CLASS}>
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100 flex items-center">
|
||||
DALL-E Image Generation <InfoTooltip content="Configure the DALL-E model used for generating project cover art. Uses the same provider (OpenAI / Azure) as the chat model above." />
|
||||
</h2>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Used to generate AI cover art for projects. Leave blank to disable AI cover generation.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label className={LABEL_CLASS}>
|
||||
<span className="flex items-center">
|
||||
Deployment Name <InfoTooltip content="The DALL-E model deployment name (e.g. dall-e-3). For OpenAI this is the model name, for Azure it is the deployment name." />
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLASS}
|
||||
value={dalleDeployment}
|
||||
onChange={(e) => setDalleDeployment(e.target.value)}
|
||||
placeholder="dall-e-3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{provider === "azure" && (
|
||||
<>
|
||||
<div>
|
||||
<label className={LABEL_CLASS}>
|
||||
<span className="flex items-center">
|
||||
Endpoint <InfoTooltip content="Azure endpoint for the DALL-E deployment. Leave empty to use the same endpoint as the chat model." />
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLASS}
|
||||
value={dalleEndpoint}
|
||||
onChange={(e) => setDalleEndpoint(e.target.value)}
|
||||
placeholder="Leave empty to use same endpoint as chat"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={LABEL_CLASS}>
|
||||
<span className="flex items-center">
|
||||
API Key{" "}
|
||||
<InfoTooltip content="API key for the DALL-E endpoint. Leave empty to use the same API key as the chat model." />
|
||||
<span className="ml-1 text-xs font-normal text-gray-400">(optional)</span>
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className={INPUT_CLASS}
|
||||
value={dalleApiKey}
|
||||
onChange={(e) => setDalleApiKey(e.target.value)}
|
||||
placeholder="Leave empty to use same API key as chat"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── SMTP / Email ──────────────────────────────────────────── */}
|
||||
<div className={PANEL_CLASS}>
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100">
|
||||
Email Notifications (SMTP)
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100 flex items-center">
|
||||
Email Notifications (SMTP) <InfoTooltip content="Configure SMTP to send email notifications for vacation approvals/rejections. Without SMTP, only in-app notifications are sent." />
|
||||
</h2>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Used to send email notifications when vacation requests are approved or rejected.
|
||||
@@ -1002,7 +1079,7 @@ export function SystemSettingsClient() {
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label className={LABEL_CLASS}>SMTP Host</label>
|
||||
<label className={LABEL_CLASS}><span className="flex items-center">SMTP Host <InfoTooltip content="The SMTP server hostname (e.g. smtp.gmail.com, smtp.office365.com)." /></span></label>
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLASS}
|
||||
@@ -1012,7 +1089,7 @@ export function SystemSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className={LABEL_CLASS}>SMTP Port</label>
|
||||
<label className={LABEL_CLASS}><span className="flex items-center">SMTP Port <InfoTooltip content="Common ports: 587 (STARTTLS), 465 (SSL/TLS), 25 (unencrypted). Use 587 for most providers." /></span></label>
|
||||
<input
|
||||
type="number"
|
||||
className={INPUT_CLASS}
|
||||
@@ -1023,7 +1100,7 @@ export function SystemSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className={LABEL_CLASS}>SMTP Username</label>
|
||||
<label className={LABEL_CLASS}><span className="flex items-center">SMTP Username <InfoTooltip content="Authentication username for the SMTP server. Often the same as the email address." /></span></label>
|
||||
<input
|
||||
type="text"
|
||||
className={INPUT_CLASS}
|
||||
@@ -1034,8 +1111,8 @@ export function SystemSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className={LABEL_CLASS}>
|
||||
SMTP Password{" "}
|
||||
<label className={LABEL_CLASS}><span className="flex items-center">
|
||||
SMTP Password <InfoTooltip content="The SMTP authentication password. Stored encrypted. Leave blank to keep the existing password." />{" "}</span>
|
||||
{settings?.hasSmtpPassword && (
|
||||
<span className="text-gray-400 font-normal text-xs">
|
||||
(set — leave blank to keep)
|
||||
@@ -1052,7 +1129,7 @@ export function SystemSettingsClient() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className={LABEL_CLASS}>From Address</label>
|
||||
<label className={LABEL_CLASS}><span className="flex items-center">From Address <InfoTooltip content="The sender email address shown in notification emails (e.g. noreply@planarchy.app)." /></span></label>
|
||||
<input
|
||||
type="email"
|
||||
className={INPUT_CLASS}
|
||||
@@ -1111,8 +1188,8 @@ export function SystemSettingsClient() {
|
||||
{/* ── Vacation Defaults ─────────────────────────────────────── */}
|
||||
<div className={PANEL_CLASS}>
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100">
|
||||
Vacation Defaults
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100 flex items-center">
|
||||
Vacation Defaults <InfoTooltip content="Sets the default vacation entitlement applied when creating new resources or using the bulk-set tool in Vacation Management." />
|
||||
</h2>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Default annual leave entitlement for new resources and the entitlement bulk-set tool.
|
||||
@@ -1120,7 +1197,7 @@ export function SystemSettingsClient() {
|
||||
</div>
|
||||
|
||||
<div className="max-w-xs">
|
||||
<label className={LABEL_CLASS}>Default Annual Leave Days</label>
|
||||
<label className={LABEL_CLASS}><span className="flex items-center">Default Annual Leave Days <InfoTooltip content="The number of vacation days granted per year. In Germany, the legal minimum is 20 days; 28-30 is common. This value is used when creating new entitlement records." /></span></label>
|
||||
<input
|
||||
type="number"
|
||||
className={INPUT_CLASS}
|
||||
@@ -1151,8 +1228,8 @@ export function SystemSettingsClient() {
|
||||
|
||||
<div className={PANEL_CLASS}>
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100">
|
||||
Viewer Anonymization
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-gray-100 flex items-center">
|
||||
Viewer Anonymization <InfoTooltip content="When enabled, all resource names, EIDs, and emails are replaced with stable fictional aliases (e.g. superhero names) in the UI. Real data stays in the database. Useful for demos and screenshots." />
|
||||
</h2>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Global debug mode that keeps real identities in the database but replaces displayed
|
||||
|
||||
Reference in New Issue
Block a user