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:
@@ -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 “{searchQuery}”.
|
||||
</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,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",
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user