total-recall-brain 3.35.0 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/total-recall.mjs +17 -4
- package/frontend/dist/assets/{index-BPrkIYkZ.js → index-DZ4ypNEl.js} +135 -135
- package/frontend/dist/brand/plugins/phone.svg +20 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +3 -30
- package/frontend/src/api/plugins.ts +18 -0
- package/frontend/src/components/plugins/previews/CodeQualityPreview.tsx +78 -86
- package/frontend/src/components/plugins/previews/ComposableCliPreview.tsx +74 -84
- package/frontend/src/components/plugins/previews/DecisionPreview.tsx +158 -106
- package/frontend/src/components/plugins/previews/DesignPreview.tsx +109 -75
- package/frontend/src/components/plugins/previews/DomainsPreview.tsx +69 -88
- package/frontend/src/components/plugins/previews/ImageGeneratorPreview.tsx +286 -0
- package/frontend/src/components/plugins/previews/PhonePreview.tsx +490 -76
- package/frontend/src/components/plugins/previews/SigningPreview.tsx +164 -50
- package/frontend/src/components/plugins/previews/TextPreview.tsx +85 -86
- package/frontend/src/components/plugins/previews/UserAutomationsPreview.tsx +30 -0
- package/frontend/src/components/plugins/previews/index.tsx +6 -1
- package/frontend/src/pages/PluginsPage.tsx +603 -136
- package/package.json +2 -5
- package/scaffold/.agent/skills/plugins/SKILL.md +200 -6
- package/src/cli/plugin/create.mjs +156 -11
- package/src/cli/plugin/index.mjs +5 -0
- package/src/core/embeddings.mjs +2 -0
- package/src/core/plugin-loader.mjs +18 -3
- package/src/core/plugin-store.mjs +122 -11
- package/src/core/surface.mjs +29 -1
- package/src/core/vault.mjs +6 -2
- package/src/server/route-manifest.json +22 -2
- package/src/server/routes/plugins-mesh.mjs +9 -1
- package/src/server/routes/plugins.mjs +65 -1
- package/frontend/src/pages/AutomationsPage.spec.tsx +0 -61
- package/frontend/src/pages/AutomationsPage.tsx +0 -354
- package/plugins/creative-search/README.md +0 -33
- package/plugins/creative-search/cli.mjs +0 -415
- package/plugins/creative-search/config.mjs +0 -129
- package/plugins/creative-search/generator.mjs +0 -13
- package/plugins/creative-search/plugin.json +0 -61
- package/plugins/creative-search/ui/DESIGN.md +0 -50
- package/plugins/creative-search/ui/csearch-main.js +0 -314
- package/plugins/creative-search/ui/csearch-settings.js +0 -392
- package/plugins/creative-search/ui/index.js +0 -2
- package/plugins/creative-search/ui/tokens.css +0 -34
- package/plugins/dsh/README.md +0 -20
- package/plugins/dsh/cli.mjs +0 -232
- package/plugins/dsh/generator.mjs +0 -65
- package/plugins/dsh/plugin.json +0 -57
- package/plugins/dsh/ui/DESIGN.md +0 -48
- package/plugins/dsh/ui/dsh-main.js +0 -226
- package/plugins/dsh/ui/dsh-status.js +0 -265
- package/plugins/dsh/ui/index.js +0 -2
- package/plugins/dsh/ui/tokens.css +0 -32
- package/plugins/git-sentinel/README.md +0 -29
- package/plugins/git-sentinel/cli.mjs +0 -89
- package/plugins/git-sentinel/plugin.json +0 -18
- package/plugins/operator-alerts/README.md +0 -32
- package/plugins/operator-alerts/cli.mjs +0 -57
- package/plugins/operator-alerts/notify-core.mjs +0 -216
- package/plugins/operator-alerts/plugin.json +0 -20
- package/plugins/skill-manager/README.md +0 -57
- package/plugins/skill-manager/cli.mjs +0 -123
- package/plugins/skill-manager/plugin.json +0 -25
- package/plugins/skill-manager/route.mjs +0 -106
- package/plugins/system-monitor/README.md +0 -28
- package/plugins/system-monitor/cli.mjs +0 -80
- package/plugins/system-monitor/generator.mjs +0 -19
- package/plugins/system-monitor/plugin.json +0 -29
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type CSSProperties } from "react"
|
|
1
|
+
import { useState, type CSSProperties } from "react"
|
|
2
|
+
import { runPluginCommand } from "../../../api"
|
|
2
3
|
|
|
3
4
|
const styles: Record<string, CSSProperties> = {
|
|
4
5
|
container: {
|
|
@@ -12,6 +13,7 @@ const styles: Record<string, CSSProperties> = {
|
|
|
12
13
|
header: {
|
|
13
14
|
display: "flex",
|
|
14
15
|
justifyContent: "space-between",
|
|
16
|
+
alignItems: "center",
|
|
15
17
|
marginBottom: "16px",
|
|
16
18
|
},
|
|
17
19
|
label: {
|
|
@@ -26,114 +28,93 @@ const styles: Record<string, CSSProperties> = {
|
|
|
26
28
|
padding: "4px 10px",
|
|
27
29
|
borderRadius: "20px",
|
|
28
30
|
fontSize: "11px",
|
|
31
|
+
fontWeight: 600,
|
|
29
32
|
},
|
|
30
|
-
|
|
33
|
+
inputRow: {
|
|
31
34
|
display: "flex",
|
|
32
35
|
gap: "8px",
|
|
33
|
-
marginBottom: "
|
|
36
|
+
marginBottom: "16px",
|
|
34
37
|
},
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
input: {
|
|
39
|
+
flex: 1,
|
|
40
|
+
background: "#0d1117",
|
|
37
41
|
border: "1px solid #2a2a4a",
|
|
38
42
|
borderRadius: "6px",
|
|
39
|
-
padding: "
|
|
40
|
-
color: "#
|
|
41
|
-
fontSize: "
|
|
42
|
-
|
|
43
|
+
padding: "8px 12px",
|
|
44
|
+
color: "#fff",
|
|
45
|
+
fontSize: "13px",
|
|
46
|
+
fontFamily: "inherit",
|
|
43
47
|
},
|
|
44
|
-
|
|
45
|
-
background: "#
|
|
46
|
-
border: "
|
|
48
|
+
button: {
|
|
49
|
+
background: "linear-gradient(135deg, #0288d1, #01579b)",
|
|
50
|
+
border: "none",
|
|
47
51
|
borderRadius: "6px",
|
|
48
|
-
padding: "
|
|
49
|
-
color: "#
|
|
52
|
+
padding: "8px 16px",
|
|
53
|
+
color: "#fff",
|
|
50
54
|
fontSize: "12px",
|
|
55
|
+
fontWeight: 600,
|
|
51
56
|
cursor: "pointer",
|
|
52
57
|
},
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
letterSpacing: "1px",
|
|
65
|
-
},
|
|
66
|
-
td: {
|
|
67
|
-
padding: "10px 12px",
|
|
68
|
-
fontSize: "13px",
|
|
69
|
-
borderBottom: "1px solid #1a1a2e",
|
|
70
|
-
},
|
|
71
|
-
typeA: { color: "#64b5f6" },
|
|
72
|
-
typeAAAA: { color: "#ce93d8" },
|
|
73
|
-
typeCname: { color: "#4caf50" },
|
|
74
|
-
typeMx: { color: "#ffb74d" },
|
|
75
|
-
typeTxt: { color: "#e0e0e0" },
|
|
76
|
-
ttl: {
|
|
77
|
-
color: "#666",
|
|
78
|
-
fontSize: "11px",
|
|
58
|
+
resultBox: {
|
|
59
|
+
background: "#090d13",
|
|
60
|
+
border: "1px solid #2a2a4a",
|
|
61
|
+
borderRadius: "8px",
|
|
62
|
+
padding: "14px",
|
|
63
|
+
fontSize: "12px",
|
|
64
|
+
lineHeight: 1.5,
|
|
65
|
+
maxHeight: "260px",
|
|
66
|
+
overflowY: "auto" as const,
|
|
67
|
+
whiteSpace: "pre-wrap" as const,
|
|
68
|
+
color: "#b0bec5",
|
|
79
69
|
},
|
|
80
70
|
}
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
ttl: string
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const records: RecordRow[] = [
|
|
90
|
-
{ type: "A", name: "@", value: "192.168.1.10", ttl: "300" },
|
|
91
|
-
{ type: "AAAA", name: "@", value: "fd12::1", ttl: "300" },
|
|
92
|
-
{ type: "CNAME", name: "mail", value: "mail.example.com", ttl: "3600" },
|
|
93
|
-
{ type: "MX", name: "@", value: "mail.example.com (priority 10)", ttl: "3600" },
|
|
94
|
-
{ type: "TXT", name: "@", value: '"v=spf1 include:_spf.example.com ~all"', ttl: "86400" },
|
|
95
|
-
]
|
|
72
|
+
export function DomainsPreview() {
|
|
73
|
+
const [domain, setDomain] = useState("localhost")
|
|
74
|
+
const [loading, setLoading] = useState(false)
|
|
75
|
+
const [result, setResult] = useState<string>("Enter a domain name to inspect DNS routing and registrar status.")
|
|
96
76
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
77
|
+
const handleLookup = async () => {
|
|
78
|
+
if (!domain.trim()) return
|
|
79
|
+
setLoading(true)
|
|
80
|
+
setResult(`Querying DNS records for ${domain.trim()}...`)
|
|
81
|
+
try {
|
|
82
|
+
const res = await runPluginCommand("domains", "inspect", [domain.trim()])
|
|
83
|
+
if (res.success && res.output) {
|
|
84
|
+
setResult(res.output)
|
|
85
|
+
} else {
|
|
86
|
+
setResult(res.output || res.error || `No records found or command executed: total-recall domains inspect ${domain.trim()}`)
|
|
87
|
+
}
|
|
88
|
+
} catch (err: any) {
|
|
89
|
+
setResult(`Lookup failed: ${err?.message || "Verify network connection"}`)
|
|
90
|
+
} finally {
|
|
91
|
+
setLoading(false)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
104
94
|
|
|
105
|
-
export function DomainsPreview() {
|
|
106
95
|
return (
|
|
107
96
|
<div style={styles.container}>
|
|
108
97
|
<div style={styles.header}>
|
|
109
|
-
<span style={styles.label}>DNS
|
|
110
|
-
<span style={styles.badge}>
|
|
98
|
+
<span style={styles.label}>DNS & Domain Resolution</span>
|
|
99
|
+
<span style={styles.badge}>{loading ? "Querying…" : "Online"}</span>
|
|
111
100
|
</div>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
101
|
+
|
|
102
|
+
<div style={styles.inputRow}>
|
|
103
|
+
<input
|
|
104
|
+
style={styles.input}
|
|
105
|
+
value={domain}
|
|
106
|
+
onChange={(e) => setDomain(e.target.value)}
|
|
107
|
+
placeholder="example.com"
|
|
108
|
+
onKeyDown={(e) => e.key === "Enter" && handleLookup()}
|
|
109
|
+
/>
|
|
110
|
+
<button style={styles.button} onClick={handleLookup} disabled={loading || !domain.trim()}>
|
|
111
|
+
{loading ? "Inspecting…" : "Inspect DNS"}
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div style={styles.resultBox}>
|
|
116
|
+
{result}
|
|
116
117
|
</div>
|
|
117
|
-
<table style={styles.table}>
|
|
118
|
-
<thead>
|
|
119
|
-
<tr>
|
|
120
|
-
<th style={styles.th}>Type</th>
|
|
121
|
-
<th style={styles.th}>Name</th>
|
|
122
|
-
<th style={styles.th}>Value</th>
|
|
123
|
-
<th style={styles.th}>TTL</th>
|
|
124
|
-
</tr>
|
|
125
|
-
</thead>
|
|
126
|
-
<tbody>
|
|
127
|
-
{records.map((r, i) => (
|
|
128
|
-
<tr key={i}>
|
|
129
|
-
<td style={{ ...styles.td, ...typeStyles[r.type] }}>{r.type}</td>
|
|
130
|
-
<td style={styles.td}>{r.name}</td>
|
|
131
|
-
<td style={{ ...styles.td, ...typeStyles[r.type] }}>{r.value}</td>
|
|
132
|
-
<td style={{ ...styles.td, ...styles.ttl }}>{r.ttl}s</td>
|
|
133
|
-
</tr>
|
|
134
|
-
))}
|
|
135
|
-
</tbody>
|
|
136
|
-
</table>
|
|
137
118
|
</div>
|
|
138
119
|
)
|
|
139
120
|
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { useState, type CSSProperties } from "react"
|
|
2
|
+
import { runPluginCommand } from "../../../api"
|
|
3
|
+
|
|
4
|
+
const styles: Record<string, CSSProperties> = {
|
|
5
|
+
container: {
|
|
6
|
+
background: "linear-gradient(135deg, #0f172a, #1e1b4b)",
|
|
7
|
+
borderRadius: "12px",
|
|
8
|
+
padding: "24px",
|
|
9
|
+
fontFamily: "var(--font-sans, system-ui, sans-serif)",
|
|
10
|
+
color: "#e2e8f0",
|
|
11
|
+
maxWidth: "600px",
|
|
12
|
+
},
|
|
13
|
+
header: {
|
|
14
|
+
display: "flex",
|
|
15
|
+
justifyContent: "space-between",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
marginBottom: "16px",
|
|
18
|
+
},
|
|
19
|
+
label: {
|
|
20
|
+
color: "#94a3b8",
|
|
21
|
+
fontSize: "11px",
|
|
22
|
+
textTransform: "uppercase" as const,
|
|
23
|
+
letterSpacing: "1px",
|
|
24
|
+
fontWeight: 600,
|
|
25
|
+
},
|
|
26
|
+
badge: {
|
|
27
|
+
background: "rgba(244, 63, 94, 0.2)",
|
|
28
|
+
color: "#f43f5e",
|
|
29
|
+
border: "1px solid rgba(244, 63, 94, 0.4)",
|
|
30
|
+
padding: "4px 10px",
|
|
31
|
+
borderRadius: "20px",
|
|
32
|
+
fontSize: "11px",
|
|
33
|
+
fontWeight: 600,
|
|
34
|
+
},
|
|
35
|
+
controlGroup: {
|
|
36
|
+
display: "flex",
|
|
37
|
+
flexDirection: "column",
|
|
38
|
+
gap: "6px",
|
|
39
|
+
marginBottom: "14px",
|
|
40
|
+
},
|
|
41
|
+
ctlLabel: {
|
|
42
|
+
fontSize: "12px",
|
|
43
|
+
color: "#94a3b8",
|
|
44
|
+
fontWeight: 500,
|
|
45
|
+
},
|
|
46
|
+
select: {
|
|
47
|
+
background: "#090d16",
|
|
48
|
+
border: "1px solid #334155",
|
|
49
|
+
borderRadius: "8px",
|
|
50
|
+
padding: "10px 12px",
|
|
51
|
+
color: "#f8fafc",
|
|
52
|
+
fontSize: "13px",
|
|
53
|
+
cursor: "pointer",
|
|
54
|
+
},
|
|
55
|
+
textarea: {
|
|
56
|
+
background: "#090d16",
|
|
57
|
+
border: "1px solid #334155",
|
|
58
|
+
borderRadius: "8px",
|
|
59
|
+
padding: "10px 12px",
|
|
60
|
+
color: "#f8fafc",
|
|
61
|
+
fontSize: "13px",
|
|
62
|
+
fontFamily: "inherit",
|
|
63
|
+
minHeight: "72px",
|
|
64
|
+
resize: "vertical" as const,
|
|
65
|
+
},
|
|
66
|
+
chipsRow: {
|
|
67
|
+
display: "flex",
|
|
68
|
+
gap: "6px",
|
|
69
|
+
flexWrap: "wrap",
|
|
70
|
+
marginTop: "4px",
|
|
71
|
+
},
|
|
72
|
+
chip: {
|
|
73
|
+
background: "#1e293b",
|
|
74
|
+
border: "1px solid #334155",
|
|
75
|
+
borderRadius: "6px",
|
|
76
|
+
padding: "4px 10px",
|
|
77
|
+
fontSize: "11px",
|
|
78
|
+
color: "#cbd5e1",
|
|
79
|
+
cursor: "pointer",
|
|
80
|
+
},
|
|
81
|
+
actions: {
|
|
82
|
+
display: "flex",
|
|
83
|
+
gap: "10px",
|
|
84
|
+
marginTop: "16px",
|
|
85
|
+
},
|
|
86
|
+
generateBtn: {
|
|
87
|
+
background: "linear-gradient(135deg, #f43f5e, #be123c)",
|
|
88
|
+
border: "none",
|
|
89
|
+
borderRadius: "8px",
|
|
90
|
+
padding: "12px 24px",
|
|
91
|
+
color: "#fff",
|
|
92
|
+
fontSize: "13px",
|
|
93
|
+
fontWeight: 600,
|
|
94
|
+
cursor: "pointer",
|
|
95
|
+
flex: 1,
|
|
96
|
+
display: "flex",
|
|
97
|
+
alignItems: "center",
|
|
98
|
+
justifyContent: "center",
|
|
99
|
+
gap: "8px",
|
|
100
|
+
},
|
|
101
|
+
previewCard: {
|
|
102
|
+
background: "#090d16",
|
|
103
|
+
border: "1px solid #334155",
|
|
104
|
+
borderRadius: "10px",
|
|
105
|
+
padding: "16px",
|
|
106
|
+
marginTop: "18px",
|
|
107
|
+
display: "flex",
|
|
108
|
+
flexDirection: "column",
|
|
109
|
+
alignItems: "center",
|
|
110
|
+
gap: "12px",
|
|
111
|
+
},
|
|
112
|
+
image: {
|
|
113
|
+
maxWidth: "100%",
|
|
114
|
+
maxHeight: "320px",
|
|
115
|
+
borderRadius: "8px",
|
|
116
|
+
objectFit: "contain" as const,
|
|
117
|
+
boxShadow: "0 8px 30px rgba(0,0,0,0.5)",
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const MODELS = [
|
|
122
|
+
{ id: "fal-ai/flux/schnell", name: "FLUX.1 [schnell] — Ultra-Fast (Recommended)" },
|
|
123
|
+
{ id: "fal-ai/flux/dev", name: "FLUX.1 [dev] — Detailed High Quality" },
|
|
124
|
+
{ id: "fal-ai/flux-pro/v1.1", name: "FLUX 1.1 [pro] — Professional Studio" },
|
|
125
|
+
{ id: "fal-ai/recraft-v3", name: "Recraft v3 — Vector & Logo Design" },
|
|
126
|
+
{ id: "fal-ai/fast-sdxl", name: "Fast SDXL — Lightweight Diffusion" },
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
export function ImageGeneratorPreview() {
|
|
130
|
+
const [model, setModel] = useState("fal-ai/flux/schnell")
|
|
131
|
+
const [prompt, setPrompt] = useState("minimal vector brand logo for phone dialer on dark sleek background")
|
|
132
|
+
const [size, setSize] = useState("square_hd")
|
|
133
|
+
const [loading, setLoading] = useState(false)
|
|
134
|
+
const [generatedUrl, setGeneratedUrl] = useState<string | null>(null)
|
|
135
|
+
const [statusMessage, setStatusMessage] = useState<string | null>(null)
|
|
136
|
+
|
|
137
|
+
const handleGenerate = async () => {
|
|
138
|
+
if (!prompt.trim()) return
|
|
139
|
+
setLoading(true)
|
|
140
|
+
setStatusMessage("Submitting inference request to fal.ai...")
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const args = ["--prompt", prompt.trim(), "--model", model, "--size", size, "--json"]
|
|
144
|
+
const res = await runPluginCommand("image-generator", "generate", args)
|
|
145
|
+
|
|
146
|
+
if (res.output) {
|
|
147
|
+
try {
|
|
148
|
+
const parsed = JSON.parse(res.output)
|
|
149
|
+
if (parsed.url) {
|
|
150
|
+
setGeneratedUrl(parsed.url)
|
|
151
|
+
setStatusMessage(`Successfully generated via ${model}`)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
// not json, inspect raw text
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const match = res.output.match(/URL:\s*(https?:\/\/[^\s]+)/)
|
|
159
|
+
if (match) {
|
|
160
|
+
setGeneratedUrl(match[1])
|
|
161
|
+
setStatusMessage(`Successfully generated via ${model}`)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (res.error) {
|
|
167
|
+
setStatusMessage(`Inference error: ${res.error}`)
|
|
168
|
+
} else {
|
|
169
|
+
setStatusMessage(res.output || "Generated without output URL")
|
|
170
|
+
}
|
|
171
|
+
} catch (err: any) {
|
|
172
|
+
setStatusMessage(`Generation failed: ${err?.message || "Check network/FAL_KEY"}`)
|
|
173
|
+
} finally {
|
|
174
|
+
setLoading(false)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<div style={styles.container}>
|
|
180
|
+
<div style={styles.header}>
|
|
181
|
+
<span style={styles.label}>fal.ai Generative Media Console</span>
|
|
182
|
+
<span style={styles.badge}>Live Inference</span>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Model Selector Front & Center */}
|
|
186
|
+
<div style={styles.controlGroup}>
|
|
187
|
+
<label style={styles.ctlLabel}>Inference Model</label>
|
|
188
|
+
<select
|
|
189
|
+
style={styles.select}
|
|
190
|
+
value={model}
|
|
191
|
+
onChange={(e) => setModel(e.target.value)}
|
|
192
|
+
disabled={loading}
|
|
193
|
+
>
|
|
194
|
+
{MODELS.map((m) => (
|
|
195
|
+
<option key={m.id} value={m.id}>
|
|
196
|
+
{m.name}
|
|
197
|
+
</option>
|
|
198
|
+
))}
|
|
199
|
+
</select>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{/* Prompt input */}
|
|
203
|
+
<div style={styles.controlGroup}>
|
|
204
|
+
<label style={styles.ctlLabel}>Prompt Description</label>
|
|
205
|
+
<textarea
|
|
206
|
+
style={styles.textarea}
|
|
207
|
+
value={prompt}
|
|
208
|
+
onChange={(e) => setPrompt(e.target.value)}
|
|
209
|
+
placeholder="Describe the branding image, logo, or icon to generate..."
|
|
210
|
+
disabled={loading}
|
|
211
|
+
/>
|
|
212
|
+
<div style={styles.chipsRow}>
|
|
213
|
+
<button
|
|
214
|
+
type="button"
|
|
215
|
+
style={styles.chip}
|
|
216
|
+
onClick={() => setPrompt("sleek modern app icon for telecom dialer, minimal geometry on obsidian backdrop")}
|
|
217
|
+
>
|
|
218
|
+
📞 App Icon
|
|
219
|
+
</button>
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
style={styles.chip}
|
|
223
|
+
onClick={() => setPrompt("vector brand emblem for cryptographic document signing, purple gradient, crisp lines")}
|
|
224
|
+
>
|
|
225
|
+
✍️ Brand Seal
|
|
226
|
+
</button>
|
|
227
|
+
<button
|
|
228
|
+
type="button"
|
|
229
|
+
style={styles.chip}
|
|
230
|
+
onClick={() => setPrompt("minimalist globe DNS network icon with glowing cyan nodes and dark glass badge")}
|
|
231
|
+
>
|
|
232
|
+
🌐 DNS Badge
|
|
233
|
+
</button>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
{/* Size Selector */}
|
|
238
|
+
<div style={styles.controlGroup}>
|
|
239
|
+
<label style={styles.ctlLabel}>Aspect Ratio / Dimensions</label>
|
|
240
|
+
<select
|
|
241
|
+
style={styles.select}
|
|
242
|
+
value={size}
|
|
243
|
+
onChange={(e) => setSize(e.target.value)}
|
|
244
|
+
disabled={loading}
|
|
245
|
+
>
|
|
246
|
+
<option value="square_hd">Square HD (1024 × 1024) — Best for Logos & Icons</option>
|
|
247
|
+
<option value="landscape_16_9">Landscape 16:9 — Best for Banners</option>
|
|
248
|
+
<option value="portrait_16_9">Portrait 9:16 — Best for Mobile</option>
|
|
249
|
+
</select>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div style={styles.actions}>
|
|
253
|
+
<button
|
|
254
|
+
style={styles.generateBtn}
|
|
255
|
+
onClick={handleGenerate}
|
|
256
|
+
disabled={loading || !prompt.trim()}
|
|
257
|
+
>
|
|
258
|
+
{loading ? "Generating Image with fal.ai…" : "✨ Generate Asset"}
|
|
259
|
+
</button>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
{statusMessage && (
|
|
263
|
+
<div style={{ marginTop: "12px", fontSize: "12px", color: "#94a3b8", textAlign: "center" }}>
|
|
264
|
+
{statusMessage}
|
|
265
|
+
</div>
|
|
266
|
+
)}
|
|
267
|
+
|
|
268
|
+
{generatedUrl && (
|
|
269
|
+
<div style={styles.previewCard}>
|
|
270
|
+
<img src={generatedUrl} alt="Generated Asset" style={styles.image} />
|
|
271
|
+
<div style={{ display: "flex", gap: "8px", width: "100%", justifyContent: "center" }}>
|
|
272
|
+
<a
|
|
273
|
+
href={generatedUrl}
|
|
274
|
+
target="_blank"
|
|
275
|
+
rel="noreferrer"
|
|
276
|
+
className="btn btn-ghost btn-sm"
|
|
277
|
+
style={{ fontSize: "12px", textDecoration: "none" }}
|
|
278
|
+
>
|
|
279
|
+
Open Full Resolution ↗
|
|
280
|
+
</a>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
)
|
|
286
|
+
}
|