+
+ {/* A form, so Enter saves. Pasting a key and pressing Enter is the strongest habit there
+ is in a password field, and outside a form it silently does nothing — which reads as
+ "the key did not save". TODO.md tracks nineteen panels with this same defect; this is
+ not the twentieth. */}
+
+
+ Stored encrypted. The server never sends a key back, so this field always starts empty.
+
+
+
+
+
+ );
+ })
+ )}
+
+ );
+};
+
+function describe(provider: ProviderStatus): string {
+ const base = `${provider.adapter} · ${provider.model}`;
+ if (!provider.configured) return `${base} — no key. Set one here, or as ${provider.env_var}.`;
+ if (provider.key_source === 'environment') return `${base} — key from ${provider.env_var}.`;
+ return `${base} — key stored and encrypted.`;
+}
diff --git a/web/Providers/index.ts b/web/Providers/index.ts
new file mode 100644
index 0000000..4c75a95
--- /dev/null
+++ b/web/Providers/index.ts
@@ -0,0 +1 @@
+export { Providers } from './Providers';
diff --git a/web/layout.ts b/web/layout.ts
index 6c876d6..de8f50b 100644
--- a/web/layout.ts
+++ b/web/layout.ts
@@ -1,8 +1,15 @@
import type { LayoutNode } from 'officerdev';
// Generate stays narrow and fixed — it is a form, not a working area. Gallery gets the most room: it
-// is the main "what did I make" view. Collections is the organising view, smallest of the three. Users
-// can resize or rearrange freely; this is only the starting point.
+// is the main "what did I make" view. The right column stacks the two organising views: Collections
+// above, Providers below.
+//
+// Providers is here rather than in Officer's settings because a plugin contributes panels, not settings
+// sections — the rule `IntegrationsSettings/index.tsx` states for email. It is placed smallest and last
+// because it is configured once and then ignored, but it has to be REACHABLE, and a plugin whose only
+// configuration screen lives in the platform is not self-contained.
+//
+// Users can resize or rearrange freely; this is only the starting point.
export const defaultLayout: LayoutNode = {
type: 'group',
id: 'atelier-root',
@@ -10,6 +17,17 @@ export const defaultLayout: LayoutNode = {
children: [
{ node: { type: 'panel', id: 'atelier-generate', appType: 'atelier-generate' }, size: 26 },
{ node: { type: 'panel', id: 'atelier-gallery', appType: 'atelier-gallery' }, size: 48 },
- { node: { type: 'panel', id: 'atelier-collections', appType: 'atelier-collections' }, size: 26 },
+ {
+ node: {
+ type: 'group',
+ id: 'atelier-side',
+ direction: 'vertical',
+ children: [
+ { node: { type: 'panel', id: 'atelier-collections', appType: 'atelier-collections' }, size: 60 },
+ { node: { type: 'panel', id: 'atelier-providers', appType: 'atelier-providers' }, size: 40 },
+ ],
+ },
+ size: 26,
+ },
],
};
diff --git a/web/panels.ts b/web/panels.ts
index 7eb3438..27b3bd9 100644
--- a/web/panels.ts
+++ b/web/panels.ts
@@ -1,8 +1,9 @@
import type { AppRegistryMeta } from 'officerdev';
-import { Sparkles, GalleryHorizontalEnd, FolderOpen } from 'lucide-react';
+import { Sparkles, GalleryHorizontalEnd, FolderOpen, KeyRound } from 'lucide-react';
import { Generate } from './Generate/Generate';
import { Gallery } from './Gallery/Gallery';
import { Collections } from './Collections/Collections';
+import { Providers } from './Providers';
// No component re-exports. `panels.ts` declares PANELS — the shell mounts them by key and there is no
// way to export a component from a plugin.
@@ -16,4 +17,7 @@ export const appRegistryMetas: AppRegistryMeta[] = [
{ key: 'atelier-generate', name: 'Generate', icon: Sparkles, component: Generate, availableOnPanel: false },
{ key: 'atelier-gallery', name: 'Gallery', icon: GalleryHorizontalEnd, component: Gallery, availableOnPanel: false },
{ key: 'atelier-collections', name: 'Collections', icon: FolderOpen, component: Collections, availableOnPanel: false },
+ // Providers is a panel and not a section in Officer's settings — see Providers.tsx. A plugin that
+ // cannot be configured without the platform shipping its settings screen is not self-contained.
+ { key: 'atelier-providers', name: 'Providers', icon: KeyRound, component: Providers, availableOnPanel: false },
];