feat(3.15): AI Tools extindere si integrare

Prompt Generator:
- Search bar cu cautare in name/description/tags/category
- Filtru target type (text/image) cu toggle rapid 'Imagine'
- 4 template-uri noi imagine: Midjourney Exterior, SD Interior,
  Midjourney Infographic, SD Material Texture (18 total)
- Config v0.2.0

AI Chat  Real API Integration:
- /api/ai-chat route: multi-provider (OpenAI, Anthropic, Ollama, demo)
- System prompt default in romana pt context arhitectura
- GET: config status, POST: message routing
- use-chat.ts: sendMessage() cu fetch real, sending state,
  providerConfig fetch, updateSession() pt project linking
- UI: provider status badge (Wifi/WifiOff), Bot icon pe mesaje,
  loading spinner la generare, disable input while sending
- Config banner cu detalii provider/model/stare

AI Chat + Tag Manager:
- Project selector dropdown in chat header (useTags project)
- Session linking: projectTagId + projectName on ChatSession
- Project name display in session sidebar
- Project context injected in system prompt

Docker:
- AI env vars: AI_PROVIDER, AI_API_KEY, AI_MODEL, AI_BASE_URL, AI_MAX_TOKENS
This commit is contained in:
AI Assistant
2026-02-28 04:51:36 +02:00
parent 11b35c750f
commit d34c722167
12 changed files with 1550 additions and 189 deletions
@@ -1,52 +1,87 @@
'use client';
"use client";
import { useState } from 'react';
import { ArrowLeft, Copy, Check, Save, Trash2, History, Sparkles } from 'lucide-react';
import { Button } from '@/shared/components/ui/button';
import { Input } from '@/shared/components/ui/input';
import { Label } from '@/shared/components/ui/label';
import { Textarea } from '@/shared/components/ui/textarea';
import { Badge } from '@/shared/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/shared/components/ui/card';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select';
import { Separator } from '@/shared/components/ui/separator';
import type { PromptTemplate, PromptVariable } from '../types';
import { usePromptGenerator } from '../hooks/use-prompt-generator';
import { cn } from '@/shared/lib/utils';
import { useState } from "react";
import {
ArrowLeft,
Copy,
Check,
Save,
Trash2,
History,
Sparkles,
Search,
Image,
} from "lucide-react";
import { Button } from "@/shared/components/ui/button";
import { Input } from "@/shared/components/ui/input";
import { Label } from "@/shared/components/ui/label";
import { Textarea } from "@/shared/components/ui/textarea";
import { Badge } from "@/shared/components/ui/badge";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/shared/components/ui/card";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shared/components/ui/select";
import { Separator } from "@/shared/components/ui/separator";
import type { PromptTemplate, PromptVariable } from "../types";
import { usePromptGenerator } from "../hooks/use-prompt-generator";
import { cn } from "@/shared/lib/utils";
const CATEGORY_LABELS: Record<string, string> = {
architecture: 'Arhitectură',
legal: 'Legal',
technical: 'Tehnic',
administrative: 'Administrativ',
gis: 'GIS',
bim: 'BIM',
rendering: 'Vizualizare',
procurement: 'Achiziții',
general: 'General',
architecture: "Arhitectură",
legal: "Legal",
technical: "Tehnic",
administrative: "Administrativ",
gis: "GIS",
bim: "BIM",
rendering: "Vizualizare",
procurement: "Achiziții",
general: "General",
};
const TARGET_LABELS: Record<string, string> = {
text: 'Text', image: 'Imagine', code: 'Cod', review: 'Review', rewrite: 'Rescriere',
text: "Text",
image: "Imagine",
code: "Cod",
review: "Review",
rewrite: "Rescriere",
};
type ViewMode = 'templates' | 'compose' | 'history';
type ViewMode = "templates" | "compose" | "history";
export function PromptGeneratorModule() {
const {
allTemplates, selectedTemplate, values, composedPrompt,
history, selectTemplate, updateValue, saveToHistory,
deleteHistoryEntry, clearSelection,
allTemplates,
selectedTemplate,
values,
composedPrompt,
history,
selectTemplate,
updateValue,
saveToHistory,
deleteHistoryEntry,
clearSelection,
} = usePromptGenerator();
const [viewMode, setViewMode] = useState<ViewMode>('templates');
const [viewMode, setViewMode] = useState<ViewMode>("templates");
const [copied, setCopied] = useState(false);
const [saved, setSaved] = useState(false);
const [filterCategory, setFilterCategory] = useState<string>('all');
const [filterCategory, setFilterCategory] = useState<string>("all");
const [filterTarget, setFilterTarget] = useState<string>("all");
const [searchQuery, setSearchQuery] = useState("");
const handleSelectTemplate = (template: PromptTemplate) => {
selectTemplate(template);
setViewMode('compose');
setViewMode("compose");
};
const handleCopy = async () => {
@@ -54,7 +89,9 @@ export function PromptGeneratorModule() {
await navigator.clipboard.writeText(composedPrompt);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch { /* silent */ }
} catch {
/* silent */
}
};
const handleSave = async () => {
@@ -65,80 +102,149 @@ export function PromptGeneratorModule() {
const handleBack = () => {
clearSelection();
setViewMode('templates');
setViewMode("templates");
};
const filteredTemplates = filterCategory === 'all'
? allTemplates
: allTemplates.filter((t) => t.category === filterCategory);
const filteredTemplates = allTemplates.filter((t) => {
if (filterCategory !== "all" && t.category !== filterCategory) return false;
if (filterTarget !== "all" && t.targetAiType !== filterTarget) return false;
if (searchQuery) {
const q = searchQuery.toLowerCase();
return (
t.name.toLowerCase().includes(q) ||
t.description.toLowerCase().includes(q) ||
t.tags.some((tag) => tag.toLowerCase().includes(q)) ||
(CATEGORY_LABELS[t.category] ?? "").toLowerCase().includes(q)
);
}
return true;
});
const usedCategories = [...new Set(allTemplates.map((t) => t.category))];
const usedTargets = [...new Set(allTemplates.map((t) => t.targetAiType))];
return (
<div className="space-y-6">
{/* Navigation */}
<div className="flex items-center gap-2">
<Button
variant={viewMode === 'templates' ? 'default' : 'outline'}
variant={viewMode === "templates" ? "default" : "outline"}
size="sm"
onClick={() => { clearSelection(); setViewMode('templates'); }}
onClick={() => {
clearSelection();
setViewMode("templates");
}}
>
<Sparkles className="mr-1 h-3.5 w-3.5" /> Șabloane
</Button>
<Button
variant={viewMode === 'history' ? 'default' : 'outline'}
variant={viewMode === "history" ? "default" : "outline"}
size="sm"
onClick={() => setViewMode('history')}
onClick={() => setViewMode("history")}
>
<History className="mr-1 h-3.5 w-3.5" /> Istoric ({history.length})
</Button>
</div>
{/* Template browser */}
{viewMode === 'templates' && (
{viewMode === "templates" && (
<div className="space-y-4">
<div className="flex items-center gap-3">
<Label>Categorie:</Label>
<div className="flex flex-wrap items-center gap-3">
<div className="relative min-w-[200px] flex-1">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Caută șabloane..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Select value={filterCategory} onValueChange={setFilterCategory}>
<SelectTrigger className="w-[180px]"><SelectValue /></SelectTrigger>
<SelectTrigger className="w-[160px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Toate</SelectItem>
<SelectItem value="all">Toate categoriile</SelectItem>
{usedCategories.map((cat) => (
<SelectItem key={cat} value={cat}>{CATEGORY_LABELS[cat] ?? cat}</SelectItem>
<SelectItem key={cat} value={cat}>
{CATEGORY_LABELS[cat] ?? cat}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={filterTarget} onValueChange={setFilterTarget}>
<SelectTrigger className="w-[140px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Toate tipurile</SelectItem>
{usedTargets.map((t) => (
<SelectItem key={t} value={t}>
{TARGET_LABELS[t] ?? t}
</SelectItem>
))}
</SelectContent>
</Select>
{allTemplates.filter((t) => t.targetAiType === "image").length >
0 && (
<Button
variant={filterTarget === "image" ? "default" : "outline"}
size="sm"
onClick={() =>
setFilterTarget(filterTarget === "image" ? "all" : "image")
}
>
<Image className="mr-1 h-3.5 w-3.5" /> Imagine
</Button>
)}
</div>
<div className="grid gap-3 sm:grid-cols-2">
{filteredTemplates.map((template) => (
<Card
key={template.id}
className="cursor-pointer transition-colors hover:border-primary/50 hover:bg-accent/30"
onClick={() => handleSelectTemplate(template)}
>
<CardHeader className="pb-2">
<div className="flex items-center gap-2">
<CardTitle className="text-sm">{template.name}</CardTitle>
<Badge variant="outline" className="text-[10px]">{TARGET_LABELS[template.targetAiType]}</Badge>
</div>
<CardDescription className="text-xs">{template.description}</CardDescription>
</CardHeader>
<CardContent className="pt-0">
<div className="flex flex-wrap gap-1">
<Badge variant="secondary" className="text-[10px]">{CATEGORY_LABELS[template.category] ?? template.category}</Badge>
<Badge variant="secondary" className="text-[10px]">{template.variables.length} variabile</Badge>
<Badge variant="secondary" className="text-[10px]">{template.blocks.length} blocuri</Badge>
</div>
</CardContent>
</Card>
))}
</div>
{filteredTemplates.length === 0 ? (
<p className="py-8 text-center text-sm text-muted-foreground">
Niciun șablon găsit pentru &ldquo;{searchQuery}&rdquo;.
</p>
) : (
<div className="grid gap-3 sm:grid-cols-2">
{filteredTemplates.map((template) => (
<Card
key={template.id}
className="cursor-pointer transition-colors hover:border-primary/50 hover:bg-accent/30"
onClick={() => handleSelectTemplate(template)}
>
<CardHeader className="pb-2">
<div className="flex items-center gap-2">
<CardTitle className="text-sm">{template.name}</CardTitle>
<Badge variant="outline" className="text-[10px]">
{TARGET_LABELS[template.targetAiType]}
</Badge>
</div>
<CardDescription className="text-xs">
{template.description}
</CardDescription>
</CardHeader>
<CardContent className="pt-0">
<div className="flex flex-wrap gap-1">
<Badge variant="secondary" className="text-[10px]">
{CATEGORY_LABELS[template.category] ??
template.category}
</Badge>
<Badge variant="secondary" className="text-[10px]">
{template.variables.length} variabile
</Badge>
<Badge variant="secondary" className="text-[10px]">
{template.blocks.length} blocuri
</Badge>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
)}
{/* Composition view */}
{viewMode === 'compose' && selectedTemplate && (
{viewMode === "compose" && selectedTemplate && (
<div className="space-y-6">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" onClick={handleBack}>
@@ -146,14 +252,18 @@ export function PromptGeneratorModule() {
</Button>
<div>
<h3 className="font-semibold">{selectedTemplate.name}</h3>
<p className="text-xs text-muted-foreground">{selectedTemplate.description}</p>
<p className="text-xs text-muted-foreground">
{selectedTemplate.description}
</p>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-2">
{/* Variable form */}
<Card>
<CardHeader><CardTitle className="text-base">Variabile</CardTitle></CardHeader>
<CardHeader>
<CardTitle className="text-base">Variabile</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{selectedTemplate.variables.map((variable) => (
<VariableField
@@ -171,23 +281,46 @@ export function PromptGeneratorModule() {
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold">Prompt compus</h3>
<div className="flex gap-1.5">
<Button variant="outline" size="sm" onClick={handleSave} disabled={!composedPrompt}>
<Button
variant="outline"
size="sm"
onClick={handleSave}
disabled={!composedPrompt}
>
<Save className="mr-1 h-3.5 w-3.5" />
{saved ? 'Salvat!' : 'Salvează'}
{saved ? "Salvat!" : "Salvează"}
</Button>
<Button size="sm" onClick={handleCopy} disabled={!composedPrompt}>
{copied ? <Check className="mr-1 h-3.5 w-3.5" /> : <Copy className="mr-1 h-3.5 w-3.5" />}
{copied ? 'Copiat!' : 'Copiază'}
<Button
size="sm"
onClick={handleCopy}
disabled={!composedPrompt}
>
{copied ? (
<Check className="mr-1 h-3.5 w-3.5" />
) : (
<Copy className="mr-1 h-3.5 w-3.5" />
)}
{copied ? "Copiat!" : "Copiază"}
</Button>
</div>
</div>
<div className="min-h-[300px] whitespace-pre-wrap rounded-lg border bg-muted/30 p-4 text-sm">
{composedPrompt || <span className="text-muted-foreground italic">Completează variabilele pentru a genera promptul...</span>}
{composedPrompt || (
<span className="text-muted-foreground italic">
Completează variabilele pentru a genera promptul...
</span>
)}
</div>
<div className="flex flex-wrap gap-1.5">
<Badge variant="outline" className="text-[10px]">Output: {selectedTemplate.outputMode}</Badge>
<Badge variant="outline" className="text-[10px]">Blocuri: {selectedTemplate.blocks.length}</Badge>
<Badge variant="outline" className="text-[10px]">Caractere: {composedPrompt.length}</Badge>
<Badge variant="outline" className="text-[10px]">
Output: {selectedTemplate.outputMode}
</Badge>
<Badge variant="outline" className="text-[10px]">
Blocuri: {selectedTemplate.blocks.length}
</Badge>
<Badge variant="outline" className="text-[10px]">
Caractere: {composedPrompt.length}
</Badge>
</div>
</div>
</div>
@@ -195,10 +328,12 @@ export function PromptGeneratorModule() {
)}
{/* History view */}
{viewMode === 'history' && (
{viewMode === "history" && (
<div className="space-y-3">
{history.length === 0 ? (
<p className="py-8 text-center text-sm text-muted-foreground">Niciun prompt salvat în istoric.</p>
<p className="py-8 text-center text-sm text-muted-foreground">
Niciun prompt salvat în istoric.
</p>
) : (
history.map((entry) => (
<Card key={entry.id} className="group">
@@ -206,11 +341,16 @@ export function PromptGeneratorModule() {
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<p className="text-sm font-medium">{entry.templateName}</p>
<Badge variant="outline" className="text-[10px]">{entry.outputMode}</Badge>
<p className="text-sm font-medium">
{entry.templateName}
</p>
<Badge variant="outline" className="text-[10px]">
{entry.outputMode}
</Badge>
</div>
<p className="mt-1 text-xs text-muted-foreground">
{new Date(entry.createdAt).toLocaleString('ro-RO')} {entry.composedPrompt.length} caractere
{new Date(entry.createdAt).toLocaleString("ro-RO")} {" "}
{entry.composedPrompt.length} caractere
</p>
<pre className="mt-2 max-h-24 overflow-hidden truncate text-xs text-muted-foreground">
{entry.composedPrompt.slice(0, 200)}...
@@ -218,7 +358,12 @@ export function PromptGeneratorModule() {
</div>
<div className="flex gap-1 opacity-0 transition-opacity group-hover:opacity-100">
<CopyHistoryButton text={entry.composedPrompt} />
<Button variant="ghost" size="icon" className="h-7 w-7 text-destructive" onClick={() => deleteHistoryEntry(entry.id)}>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 text-destructive"
onClick={() => deleteHistoryEntry(entry.id)}
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
@@ -233,44 +378,68 @@ export function PromptGeneratorModule() {
);
}
function VariableField({ variable, value, onChange }: {
function VariableField({
variable,
value,
onChange,
}: {
variable: PromptVariable;
value: unknown;
onChange: (val: unknown) => void;
}) {
const strVal = value !== undefined && value !== null ? String(value) : '';
const strVal = value !== undefined && value !== null ? String(value) : "";
return (
<div>
<Label className={cn(variable.required && 'after:content-["*"] after:ml-0.5 after:text-destructive')}>
<Label
className={cn(
variable.required &&
'after:content-["*"] after:ml-0.5 after:text-destructive',
)}
>
{variable.label}
</Label>
{variable.helperText && (
<p className="text-[11px] text-muted-foreground">{variable.helperText}</p>
<p className="text-[11px] text-muted-foreground">
{variable.helperText}
</p>
)}
{(variable.type === 'text' || variable.type === 'number') && (
{(variable.type === "text" || variable.type === "number") && (
<Input
type={variable.type === 'number' ? 'number' : 'text'}
type={variable.type === "number" ? "number" : "text"}
value={strVal}
onChange={(e) => onChange(variable.type === 'number' ? Number(e.target.value) : e.target.value)}
onChange={(e) =>
onChange(
variable.type === "number"
? Number(e.target.value)
: e.target.value,
)
}
placeholder={variable.placeholder}
className="mt-1"
/>
)}
{(variable.type === 'select' || variable.type === 'tone-selector' || variable.type === 'company-selector') && variable.options && (
<Select value={strVal} onValueChange={onChange}>
<SelectTrigger className="mt-1"><SelectValue placeholder="Selectează..." /></SelectTrigger>
<SelectContent>
{variable.options.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
))}
</SelectContent>
</Select>
)}
{(variable.type === "select" ||
variable.type === "tone-selector" ||
variable.type === "company-selector") &&
variable.options && (
<Select value={strVal} onValueChange={onChange}>
<SelectTrigger className="mt-1">
<SelectValue placeholder="Selectează..." />
</SelectTrigger>
<SelectContent>
{variable.options.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
)}
{variable.type === 'boolean' && (
{variable.type === "boolean" && (
<div className="mt-1 flex items-center gap-2">
<input
type="checkbox"
@@ -278,26 +447,33 @@ function VariableField({ variable, value, onChange }: {
onChange={(e) => onChange(e.target.checked)}
className="h-4 w-4 rounded accent-primary"
/>
<span className="text-sm text-muted-foreground">{variable.placeholder ?? 'Da/Nu'}</span>
<span className="text-sm text-muted-foreground">
{variable.placeholder ?? "Da/Nu"}
</span>
</div>
)}
{variable.type === 'multi-select' && variable.options && (
{variable.type === "multi-select" && variable.options && (
<div className="mt-1 flex flex-wrap gap-1.5">
{variable.options.map((opt) => {
const selected = Array.isArray(value) && (value as string[]).includes(opt.value);
const selected =
Array.isArray(value) && (value as string[]).includes(opt.value);
return (
<button
key={opt.value}
type="button"
onClick={() => {
const arr = Array.isArray(value) ? [...(value as string[])] : [];
const arr = Array.isArray(value)
? [...(value as string[])]
: [];
if (selected) onChange(arr.filter((v) => v !== opt.value));
else onChange([...arr, opt.value]);
}}
className={cn(
'rounded-full border px-2.5 py-0.5 text-xs transition-colors',
selected ? 'border-primary bg-primary text-primary-foreground' : 'hover:bg-accent'
"rounded-full border px-2.5 py-0.5 text-xs transition-colors",
selected
? "border-primary bg-primary text-primary-foreground"
: "hover:bg-accent",
)}
>
{opt.label}
@@ -317,11 +493,22 @@ function CopyHistoryButton({ text }: { text: string }) {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch { /* silent */ }
} catch {
/* silent */
}
};
return (
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={handleCopy}>
{copied ? <Check className="h-3.5 w-3.5 text-green-500" /> : <Copy className="h-3.5 w-3.5" />}
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={handleCopy}
>
{copied ? (
<Check className="h-3.5 w-3.5 text-green-500" />
) : (
<Copy className="h-3.5 w-3.5" />
)}
</Button>
);
}
+3 -3
View File
@@ -3,15 +3,15 @@ import type { ModuleConfig } from '@/core/module-registry/types';
export const promptGeneratorConfig: ModuleConfig = {
id: 'prompt-generator',
name: 'Generator Prompturi',
description: 'Generator structurat de prompturi pe bază de șabloane parametrizate, organizate pe domenii profesionale',
description: 'Generator structurat de prompturi pe bază de șabloane parametrizate, cu căutare și filtrare pe domenii profesionale și tipuri AI',
icon: 'sparkles',
route: '/prompt-generator',
category: 'ai',
featureFlag: 'module.prompt-generator',
visibility: 'all',
version: '0.1.0',
version: '0.2.0',
dependencies: [],
storageNamespace: 'prompt-generator',
navOrder: 50,
tags: ['prompt', 'ai', 'generator', 'șabloane'],
tags: ['prompt', 'ai', 'generator', 'șabloane', 'midjourney', 'stable-diffusion', 'imagine'],
};
@@ -1839,4 +1839,627 @@ export const BUILTIN_TEMPLATES: PromptTemplate[] = [
author: "ArchiTools",
visibility: "all",
},
// ── Image Generation Templates (Midjourney / Stable Diffusion) ──
{
id: "midjourney-exterior",
name: "Randare Exterioară Midjourney",
category: "rendering",
domain: "architecture-visualization",
description:
"Generează un prompt optimizat pentru Midjourney v6 — randare arhitecturală exterioară fotorealistă.",
targetAiType: "image",
blocks: [
{
id: "b1",
type: "task",
label: "Subiect principal",
content:
"{{buildingType}}, {{style}} architecture, {{materials}}, located in {{environment}}",
order: 1,
required: true,
},
{
id: "b2",
type: "context",
label: "Atmosferă și lumină",
content:
"{{lighting}} lighting, {{timeOfDay}}, {{weather}}, {{season}}",
order: 2,
required: true,
},
{
id: "b3",
type: "format",
label: "Parametri tehnici",
content:
"architectural photography, {{cameraAngle}}, {{lensType}}, high detail, 8k, photorealistic --ar {{aspectRatio}} --v 6 --style raw --s {{stylize}}",
order: 3,
required: true,
},
{
id: "b4",
type: "constraints",
label: "Constrângeri negative",
content: "{{negativePrompt}}",
order: 4,
required: false,
conditional: {
variableId: "negativePrompt",
operator: "truthy",
},
},
],
variables: [
{
id: "buildingType",
label: "Tip clădire",
type: "select",
required: true,
options: [
{
value: "modern residential house",
label: "Casă rezidențială modernă",
},
{
value: "contemporary apartment building",
label: "Bloc apartamente contemporan",
},
{ value: "commercial office building", label: "Clădire birouri" },
{ value: "cultural center", label: "Centru cultural" },
{ value: "mixed-use development", label: "Complex multifuncțional" },
{ value: "villa with pool", label: "Vilă cu piscină" },
],
},
{
id: "style",
label: "Stil arhitectural",
type: "select",
required: true,
options: [
{ value: "minimalist", label: "Minimalist" },
{ value: "brutalist", label: "Brutalist" },
{ value: "parametric", label: "Parametric" },
{ value: "organic", label: "Organic" },
{ value: "neo-traditional", label: "Neo-tradițional" },
{ value: "high-tech", label: "High-tech" },
],
},
{
id: "materials",
label: "Materiale",
type: "text",
required: true,
placeholder: "ex: exposed concrete, timber cladding, glass facade",
},
{
id: "environment",
label: "Mediu înconjurător",
type: "select",
required: true,
options: [
{ value: "urban context with trees", label: "Urban cu arbori" },
{ value: "hillside with panoramic view", label: "Versant panoramic" },
{ value: "waterfront", label: "Lângă apă" },
{ value: "forest clearing", label: "Poiană în pădure" },
{ value: "dense city center", label: "Centru urban dens" },
],
},
{
id: "lighting",
label: "Iluminare",
type: "select",
required: true,
options: [
{ value: "golden hour", label: "Golden hour" },
{ value: "soft diffused", label: "Difuză" },
{ value: "dramatic", label: "Dramatică" },
{ value: "overcast ambient", label: "Ambient înnorat" },
],
},
{
id: "timeOfDay",
label: "Moment al zilei",
type: "select",
required: true,
options: [
{ value: "early morning", label: "Dimineață devreme" },
{ value: "midday", label: "Amiază" },
{ value: "late afternoon", label: "Seară" },
{ value: "blue hour dusk", label: "Blue hour / amurg" },
{
value: "night with interior lights",
label: "Noapte cu lumini interioare",
},
],
},
{
id: "weather",
label: "Vreme",
type: "select",
required: false,
options: [
{ value: "clear sky", label: "Cer senin" },
{ value: "partly cloudy", label: "Parțial înnorat" },
{ value: "light rain", label: "Ploaie ușoară" },
{ value: "snow covered", label: "Acoperit de zăpadă" },
],
},
{
id: "season",
label: "Anotimp",
type: "select",
required: false,
options: [
{ value: "spring with blossoms", label: "Primăvară" },
{ value: "lush summer", label: "Vară" },
{ value: "autumn colors", label: "Toamnă" },
{ value: "winter", label: "Iarnă" },
],
},
{
id: "cameraAngle",
label: "Unghi cameră",
type: "select",
required: true,
options: [
{ value: "eye-level perspective", label: "Nivel ochiului" },
{ value: "low angle dramatic", label: "Unghi jos dramatic" },
{ value: "aerial bird's eye", label: "Aerian" },
{ value: "three-quarter view", label: "Perspectivă 3/4" },
],
},
{
id: "lensType",
label: "Tip obiectiv",
type: "select",
required: false,
options: [
{ value: "wide angle 24mm", label: "Wide 24mm" },
{ value: "standard 50mm", label: "Standard 50mm" },
{ value: "telephoto 85mm", label: "Telephoto 85mm" },
{ value: "tilt-shift", label: "Tilt-shift" },
],
},
{
id: "aspectRatio",
label: "Aspect ratio",
type: "select",
required: true,
options: [
{ value: "16:9", label: "16:9 (landscape)" },
{ value: "4:3", label: "4:3" },
{ value: "1:1", label: "1:1 (square)" },
{ value: "9:16", label: "9:16 (portrait)" },
{ value: "3:2", label: "3:2" },
],
},
{
id: "stylize",
label: "Stylize (0-1000)",
type: "number",
required: false,
defaultValue: 250,
placeholder: "250",
helperText: "Valori mici = mai fidel textului, mari = mai artistic",
},
{
id: "negativePrompt",
label: "Prompt negativ (opțional)",
type: "text",
required: false,
placeholder: "ex: --no people, cars, text, watermark",
},
],
outputMode: "short",
tags: ["midjourney", "exterior", "randare", "fotorealist", "arhitectură"],
version: "1.0.0",
author: "ArchiTools",
visibility: "all",
},
{
id: "sd-interior-design",
name: "Design Interior — Stable Diffusion",
category: "rendering",
domain: "architecture-visualization",
description:
"Prompt optimizat pentru Stable Diffusion XL — vizualizare design interior cu control detaliat.",
targetAiType: "image",
blocks: [
{
id: "b1",
type: "task",
label: "Scenă",
content:
"interior design photograph, {{roomType}}, {{designStyle}} style, {{colorPalette}} color palette",
order: 1,
required: true,
},
{
id: "b2",
type: "context",
label: "Mobilier și detalii",
content:
"featuring {{furniture}}, {{flooring}} flooring, {{wallTreatment}} walls, {{decorElements}}",
order: 2,
required: true,
},
{
id: "b3",
type: "format",
label: "Calitate și stil",
content:
"professional interior photography, {{lightingMood}}, high resolution, 4k, detailed textures, editorial quality, architectural digest style",
order: 3,
required: true,
},
],
variables: [
{
id: "roomType",
label: "Tip cameră",
type: "select",
required: true,
options: [
{ value: "spacious living room", label: "Living spațios" },
{ value: "modern kitchen", label: "Bucătărie modernă" },
{ value: "master bedroom", label: "Dormitor principal" },
{ value: "home office", label: "Birou acasă" },
{
value: "bathroom with natural stone",
label: "Baie cu piatră naturală",
},
{ value: "open-plan loft", label: "Loft open-plan" },
],
},
{
id: "designStyle",
label: "Stil design",
type: "select",
required: true,
options: [
{ value: "Scandinavian minimalist", label: "Scandinav minimalist" },
{ value: "Japanese wabi-sabi", label: "Wabi-sabi japonez" },
{ value: "industrial chic", label: "Industrial chic" },
{ value: "mid-century modern", label: "Mid-century modern" },
{ value: "Mediterranean warm", label: "Mediteranean cald" },
{ value: "contemporary luxury", label: "Lux contemporan" },
],
},
{
id: "colorPalette",
label: "Paletă de culori",
type: "text",
required: true,
placeholder: "ex: warm neutrals with sage green accents",
},
{
id: "furniture",
label: "Mobilier principal",
type: "text",
required: true,
placeholder: "ex: modular sofa, oak dining table, pendant lights",
},
{
id: "flooring",
label: "Pardoseală",
type: "select",
required: false,
options: [
{ value: "hardwood oak", label: "Parchet stejar" },
{ value: "polished concrete", label: "Beton șlefuit" },
{ value: "marble", label: "Marmură" },
{ value: "terrazzo", label: "Terrazzo" },
{ value: "ceramic tile", label: "Gresie ceramică" },
],
},
{
id: "wallTreatment",
label: "Finisaj pereți",
type: "select",
required: false,
options: [
{ value: "smooth plaster", label: "Tencuială netedă" },
{ value: "exposed brick", label: "Cărămidă aparentă" },
{ value: "wood panel", label: "Lambriu lemn" },
{ value: "textured wallpaper", label: "Tapet texturat" },
],
},
{
id: "decorElements",
label: "Elemente decorative",
type: "text",
required: false,
placeholder: "ex: indoor plants, art prints, ceramic vases",
},
{
id: "lightingMood",
label: "Atmosferă luminoasă",
type: "select",
required: true,
options: [
{
value: "warm natural daylight streaming through large windows",
label: "Lumină naturală caldă",
},
{
value: "soft ambient evening light with warm lamps",
label: "Ambient seară cu lămpi calde",
},
{
value: "bright and airy with skylights",
label: "Luminos cu lucarnă",
},
{
value: "moody dramatic with accent lighting",
label: "Dramatic cu accente",
},
],
},
],
outputMode: "short",
tags: ["stable diffusion", "interior", "design", "vizualizare"],
version: "1.0.0",
author: "ArchiTools",
visibility: "all",
},
{
id: "midjourney-infographic",
name: "Infografic Arhitectural Midjourney",
category: "rendering",
domain: "architecture-visualization",
description:
"Prompt pentru generarea de infografice, diagrame explicate și prezentări vizuale pentru proiecte de arhitectură.",
targetAiType: "image",
blocks: [
{
id: "b1",
type: "task",
label: "Subiect",
content:
"architectural {{infographicType}}, showing {{subject}}, {{visualStyle}} visual style",
order: 1,
required: true,
},
{
id: "b2",
type: "context",
label: "Conținut",
content:
"illustrating {{keyElements}}, with {{annotations}}, target audience: {{audience}}",
order: 2,
required: true,
},
{
id: "b3",
type: "format",
label: "Stil grafic",
content:
"clean vector style, {{colorScheme}}, professional infographic layout, high contrast, editorial quality --ar {{aspectRatio}} --v 6",
order: 3,
required: true,
},
],
variables: [
{
id: "infographicType",
label: "Tip infografic",
type: "select",
required: true,
options: [
{
value: "exploded axonometric diagram",
label: "Axonometrie explodată",
},
{
value: "section diagram with labels",
label: "Secțiune cu etichete",
},
{ value: "site analysis diagram", label: "Diagramă analiză sit" },
{ value: "material palette board", label: "Paletar materiale" },
{
value: "sustainability features diagram",
label: "Diagramă sustenabilitate",
},
{
value: "construction phases timeline",
label: "Timeline faze construcție",
},
],
},
{
id: "subject",
label: "Subiect",
type: "text",
required: true,
placeholder: "ex: passive house energy flow, structural system",
},
{
id: "visualStyle",
label: "Stil vizual",
type: "select",
required: true,
options: [
{ value: "minimal flat", label: "Minimal flat" },
{ value: "isometric 3D", label: "Izometric 3D" },
{ value: "technical line drawing", label: "Desen tehnic" },
{ value: "watercolor sketch", label: "Schiță acuarelă" },
{ value: "collage mixed media", label: "Colaj mixed media" },
],
},
{
id: "keyElements",
label: "Elemente cheie",
type: "text",
required: true,
placeholder: "ex: insulation layers, ventilation system, solar panels",
},
{
id: "annotations",
label: "Adnotări",
type: "select",
required: false,
options: [
{ value: "numbered callouts", label: "Callout-uri numerotate" },
{ value: "legend with icons", label: "Legendă cu pictograme" },
{ value: "dimension lines", label: "Cote dimensionale" },
{ value: "no annotations", label: "Fără adnotări" },
],
},
{
id: "audience",
label: "Public țintă",
type: "select",
required: false,
options: [
{ value: "client presentation", label: "Prezentare client" },
{ value: "technical review", label: "Comisie tehnică" },
{ value: "public exhibition", label: "Expoziție publică" },
{ value: "academic portfolio", label: "Portofoliu academic" },
],
},
{
id: "colorScheme",
label: "Schemă de culori",
type: "text",
required: false,
placeholder: "ex: muted earth tones, black and white with accent red",
},
{
id: "aspectRatio",
label: "Aspect ratio",
type: "select",
required: true,
options: [
{ value: "16:9", label: "16:9 (landscape)" },
{ value: "1:1", label: "1:1 (square)" },
{ value: "9:16", label: "9:16 (poster)" },
{ value: "3:4", label: "3:4 (print)" },
],
},
],
outputMode: "short",
tags: ["midjourney", "infografic", "diagramă", "prezentare", "arhitectură"],
version: "1.0.0",
author: "ArchiTools",
visibility: "all",
},
{
id: "sd-material-texture",
name: "Textură Material — Stable Diffusion",
category: "rendering",
domain: "architecture-visualization",
description:
"Generează texturi seamless pentru materiale de construcție — utilizare în SketchUp, 3ds Max, Blender.",
targetAiType: "image",
blocks: [
{
id: "b1",
type: "task",
label: "Material",
content:
"seamless tileable texture of {{material}}, {{surfaceFinish}} finish, {{patternType}} pattern",
order: 1,
required: true,
},
{
id: "b2",
type: "context",
label: "Detalii",
content: "{{aging}} aging, {{scale}} scale, viewed from {{viewAngle}}",
order: 2,
required: true,
},
{
id: "b3",
type: "format",
label: "Parametri tehnici",
content:
"PBR material texture, high resolution, 4k, seamless tiling, physically based rendering, no distortion, uniform lighting --ar 1:1 --tile",
order: 3,
required: true,
},
],
variables: [
{
id: "material",
label: "Material",
type: "select",
required: true,
options: [
{ value: "natural oak wood", label: "Lemn stejar natural" },
{ value: "Carrara marble", label: "Marmură Carrara" },
{ value: "raw concrete", label: "Beton brut" },
{ value: "red brick", label: "Cărămidă roșie" },
{ value: "weathering steel corten", label: "Oțel Corten" },
{ value: "terracotta tiles", label: "Teracotă" },
],
},
{
id: "surfaceFinish",
label: "Finisaj suprafață",
type: "select",
required: true,
options: [
{ value: "matte", label: "Mat" },
{ value: "polished", label: "Lustruit" },
{ value: "rough", label: "Rugos" },
{ value: "brushed", label: "Periat" },
{ value: "sandblasted", label: "Sablat" },
],
},
{
id: "patternType",
label: "Tip pattern",
type: "select",
required: false,
options: [
{ value: "natural grain", label: "Fibră naturală" },
{ value: "herringbone", label: "Herringbone" },
{ value: "running bond", label: "Running bond" },
{ value: "random mosaic", label: "Mozaic random" },
{ value: "linear", label: "Linear" },
],
},
{
id: "aging",
label: "Uzură / Patină",
type: "select",
required: false,
options: [
{ value: "brand new pristine", label: "Nou" },
{ value: "slightly weathered", label: "Ușor patinat" },
{ value: "heavily worn vintage", label: "Foarte uzat / vintage" },
{ value: "moss and lichen covered", label: "Acoperit de mușchi" },
],
},
{
id: "scale",
label: "Scală",
type: "select",
required: false,
options: [
{ value: "close-up macro", label: "Macro close-up" },
{ value: "medium 1:1", label: "Mediu 1:1" },
{ value: "wide repeating panel", label: "Panou larg repetitiv" },
],
},
{
id: "viewAngle",
label: "Unghi vedere",
type: "select",
required: false,
options: [
{ value: "straight on orthographic", label: "Frontal ortografic" },
{ value: "slight angle", label: "Ușor înclinat" },
],
},
],
outputMode: "short",
tags: ["stable diffusion", "textură", "material", "PBR", "seamless"],
version: "1.0.0",
author: "ArchiTools",
visibility: "all",
},
];