Seth commited on
Commit ·
af27681
1
Parent(s): b9cd14b
update
Browse files
frontend/src/components/campaigns/CreateCampaignWizard.jsx
CHANGED
|
@@ -222,6 +222,7 @@ export default function CreateCampaignWizard({ open, onOpenChange, onComplete })
|
|
| 222 |
const [genPhase, setGenPhase] = useState('email');
|
| 223 |
const [genRunId, setGenRunId] = useState(0);
|
| 224 |
const eventSourceRef = useRef(null);
|
|
|
|
| 225 |
const prevGenRunIdRef = useRef(null);
|
| 226 |
|
| 227 |
const sequenceHasLinkedin = useMemo(
|
|
@@ -510,6 +511,7 @@ export default function CreateCampaignWizard({ open, onOpenChange, onComplete })
|
|
| 510 |
alert('Select products and fill all prompt templates before generating.');
|
| 511 |
return;
|
| 512 |
}
|
|
|
|
| 513 |
try {
|
| 514 |
const res = await apiFetch('/api/save-prompts', {
|
| 515 |
method: 'POST',
|
|
@@ -720,9 +722,11 @@ export default function CreateCampaignWizard({ open, onOpenChange, onComplete })
|
|
| 720 |
</span>
|
| 721 |
<h3 className="mt-2 text-lg font-semibold text-slate-900">Prompts & generation</h3>
|
| 722 |
<p className="mt-1 text-sm text-slate-600">
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
|
|
|
|
|
|
| 726 |
</p>
|
| 727 |
</div>
|
| 728 |
<ProductSelector
|
|
@@ -732,25 +736,15 @@ export default function CreateCampaignWizard({ open, onOpenChange, onComplete })
|
|
| 732 |
{selectedProducts.length > 0 ? (
|
| 733 |
<>
|
| 734 |
<PromptEditor
|
|
|
|
| 735 |
selectedProducts={selectedProducts}
|
| 736 |
prompts={prompts}
|
| 737 |
onPromptsChange={setPrompts}
|
| 738 |
-
variant="
|
|
|
|
|
|
|
|
|
|
| 739 |
/>
|
| 740 |
-
{sequenceHasLinkedin ? (
|
| 741 |
-
<div className="space-y-2">
|
| 742 |
-
<h4 className="text-base font-semibold text-slate-800">LinkedIn prompts</h4>
|
| 743 |
-
<p className="text-sm text-slate-500">
|
| 744 |
-
Required because this campaign includes LinkedIn steps.
|
| 745 |
-
</p>
|
| 746 |
-
<PromptEditor
|
| 747 |
-
selectedProducts={selectedProducts}
|
| 748 |
-
prompts={linkedinPrompts}
|
| 749 |
-
onPromptsChange={setLinkedinPrompts}
|
| 750 |
-
variant="linkedin"
|
| 751 |
-
/>
|
| 752 |
-
</div>
|
| 753 |
-
) : null}
|
| 754 |
<div className="flex flex-wrap items-center gap-3">
|
| 755 |
<Button
|
| 756 |
type="button"
|
|
|
|
| 222 |
const [genPhase, setGenPhase] = useState('email');
|
| 223 |
const [genRunId, setGenRunId] = useState(0);
|
| 224 |
const eventSourceRef = useRef(null);
|
| 225 |
+
const promptEditorRef = useRef(null);
|
| 226 |
const prevGenRunIdRef = useRef(null);
|
| 227 |
|
| 228 |
const sequenceHasLinkedin = useMemo(
|
|
|
|
| 511 |
alert('Select products and fill all prompt templates before generating.');
|
| 512 |
return;
|
| 513 |
}
|
| 514 |
+
promptEditorRef.current?.commitCampaignPrompts?.();
|
| 515 |
try {
|
| 516 |
const res = await apiFetch('/api/save-prompts', {
|
| 517 |
method: 'POST',
|
|
|
|
| 722 |
</span>
|
| 723 |
<h3 className="mt-2 text-lg font-semibold text-slate-900">Prompts & generation</h3>
|
| 724 |
<p className="mt-1 text-sm text-slate-600">
|
| 725 |
+
One template per product: Gmail prompt above the divider, LinkedIn system prompt
|
| 726 |
+
below (when your sequence includes LinkedIn). Touch counts in the text are ignored —
|
| 727 |
+
the wizard sequence is authoritative. Click{' '}
|
| 728 |
+
<span className="font-medium text-slate-800">Save template</span> or{' '}
|
| 729 |
+
<span className="font-medium text-slate-800">Generate</span> to apply edits.
|
| 730 |
</p>
|
| 731 |
</div>
|
| 732 |
<ProductSelector
|
|
|
|
| 736 |
{selectedProducts.length > 0 ? (
|
| 737 |
<>
|
| 738 |
<PromptEditor
|
| 739 |
+
ref={promptEditorRef}
|
| 740 |
selectedProducts={selectedProducts}
|
| 741 |
prompts={prompts}
|
| 742 |
onPromptsChange={setPrompts}
|
| 743 |
+
variant="campaign"
|
| 744 |
+
includeLinkedinInCampaign={sequenceHasLinkedin}
|
| 745 |
+
linkedinPrompts={linkedinPrompts}
|
| 746 |
+
onLinkedinPromptsChange={setLinkedinPrompts}
|
| 747 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
<div className="flex flex-wrap items-center gap-3">
|
| 749 |
<Button
|
| 750 |
type="button"
|
frontend/src/components/prompts/PromptEditor.jsx
CHANGED
|
@@ -1,10 +1,24 @@
|
|
| 1 |
-
import React, { useState, useEffect } from 'react';
|
| 2 |
import { FileText, Save, RotateCcw, Sparkles, CheckCircle2, Linkedin } from 'lucide-react';
|
| 3 |
import { Button } from "@/components/ui/button";
|
| 4 |
import { Textarea } from "@/components/ui/textarea";
|
| 5 |
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
| 6 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
export const DEFAULT_TEMPLATES = {
|
| 9 |
'Accounts Payable Automation': `🔒 SYSTEM PROMPT (DO NOT MODIFY)
|
| 10 |
|
|
@@ -817,13 +831,19 @@ LinkedIn DMs for finance teams about expense workflows. 3 messages, professional
|
|
| 817 |
Procurement-focused LinkedIn sequence in 3 messages. Practical questions only. Label Message 1, 2, 3.`,
|
| 818 |
};
|
| 819 |
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 827 |
const [activeTab, setActiveTab] = useState(selectedProducts[0]?.name || '');
|
| 828 |
const [savedStatus, setSavedStatus] = useState({});
|
| 829 |
const [localPrompts, setLocalPrompts] = useState({});
|
|
@@ -838,6 +858,26 @@ export default function PromptEditor({
|
|
| 838 |
}, [selectedProducts, activeTab]);
|
| 839 |
|
| 840 |
useEffect(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 841 |
const library = variant === 'linkedin' ? LINKEDIN_DEFAULT_TEMPLATES : DEFAULT_TEMPLATES;
|
| 842 |
const newPrompts = {};
|
| 843 |
selectedProducts.forEach((product) => {
|
|
@@ -855,7 +895,7 @@ export default function PromptEditor({
|
|
| 855 |
}
|
| 856 |
});
|
| 857 |
setLocalPrompts(newPrompts);
|
| 858 |
-
}, [selectedProducts, prompts, variant]);
|
| 859 |
|
| 860 |
const handlePromptChange = (productName, value) => {
|
| 861 |
setLocalPrompts(prev => ({
|
|
@@ -868,11 +908,30 @@ export default function PromptEditor({
|
|
| 868 |
}));
|
| 869 |
};
|
| 870 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 871 |
const handleSave = (productName) => {
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 876 |
setSavedStatus(prev => ({
|
| 877 |
...prev,
|
| 878 |
[productName]: true
|
|
@@ -894,6 +953,23 @@ export default function PromptEditor({
|
|
| 894 |
};
|
| 895 |
|
| 896 |
const handleRestoreDefault = (productName) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 897 |
const library = variant === 'linkedin' ? LINKEDIN_DEFAULT_TEMPLATES : DEFAULT_TEMPLATES;
|
| 898 |
const defaultTemplate =
|
| 899 |
library[productName] ||
|
|
@@ -903,6 +979,42 @@ export default function PromptEditor({
|
|
| 903 |
handlePromptChange(productName, defaultTemplate);
|
| 904 |
};
|
| 905 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 906 |
if (selectedProducts.length === 0) {
|
| 907 |
return (
|
| 908 |
<div className="rounded-2xl border border-slate-200 bg-slate-50/50 p-12 text-center">
|
|
@@ -945,6 +1057,11 @@ export default function PromptEditor({
|
|
| 945 |
<div className="rounded-lg bg-violet-100 p-2">
|
| 946 |
{variant === 'linkedin' ? (
|
| 947 |
<Linkedin className="h-4 w-4 text-violet-600" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 948 |
) : (
|
| 949 |
<Sparkles className="h-4 w-4 text-violet-600" />
|
| 950 |
)}
|
|
@@ -953,10 +1070,28 @@ export default function PromptEditor({
|
|
| 953 |
<h4 className="font-semibold text-slate-800">
|
| 954 |
{variant === 'linkedin'
|
| 955 |
? 'LinkedIn sequence prompt'
|
| 956 |
-
:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 957 |
</h4>
|
| 958 |
<p className="text-xs text-slate-500">
|
| 959 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 960 |
</p>
|
| 961 |
</div>
|
| 962 |
</div>
|
|
@@ -993,13 +1128,24 @@ export default function PromptEditor({
|
|
| 993 |
<Textarea
|
| 994 |
value={localPrompts[product.name] || ''}
|
| 995 |
onChange={(e) => handlePromptChange(product.name, e.target.value)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 996 |
placeholder={
|
| 997 |
variant === 'linkedin'
|
| 998 |
? 'Enter your LinkedIn sequence system prompt…'
|
| 999 |
-
:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1000 |
}
|
| 1001 |
-
className=
|
| 1002 |
-
|
|
|
|
|
|
|
| 1003 |
/>
|
| 1004 |
</div>
|
| 1005 |
{/* Save button at bottom for easy access after scrolling */}
|
|
@@ -1029,4 +1175,6 @@ export default function PromptEditor({
|
|
| 1029 |
</Tabs>
|
| 1030 |
</div>
|
| 1031 |
);
|
| 1032 |
-
}
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect, forwardRef, useImperativeHandle, useCallback } from 'react';
|
| 2 |
import { FileText, Save, RotateCcw, Sparkles, CheckCircle2, Linkedin } from 'lucide-react';
|
| 3 |
import { Button } from "@/components/ui/button";
|
| 4 |
import { Textarea } from "@/components/ui/textarea";
|
| 5 |
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
| 6 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 7 |
|
| 8 |
+
/** Do not remove this line in combined campaign prompts — it separates Gmail vs LinkedIn system context. */
|
| 9 |
+
export const CAMPAIGN_COMBINED_PROMPT_SPLIT = '\n\n<<<CAMPAIGN_LINKEDIN_PROMPT>>>\n\n';
|
| 10 |
+
|
| 11 |
+
function splitCampaignRaw(raw, includeLinkedinSection) {
|
| 12 |
+
if (!includeLinkedinSection) {
|
| 13 |
+
return { email: raw, linkedin: '' };
|
| 14 |
+
}
|
| 15 |
+
const idx = raw.indexOf(CAMPAIGN_COMBINED_PROMPT_SPLIT);
|
| 16 |
+
return {
|
| 17 |
+
email: (idx === -1 ? raw : raw.slice(0, idx)).trimEnd(),
|
| 18 |
+
linkedin: (idx === -1 ? '' : raw.slice(idx + CAMPAIGN_COMBINED_PROMPT_SPLIT.length)).trim(),
|
| 19 |
+
};
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
export const DEFAULT_TEMPLATES = {
|
| 23 |
'Accounts Payable Automation': `🔒 SYSTEM PROMPT (DO NOT MODIFY)
|
| 24 |
|
|
|
|
| 831 |
Procurement-focused LinkedIn sequence in 3 messages. Practical questions only. Label Message 1, 2, 3.`,
|
| 832 |
};
|
| 833 |
|
| 834 |
+
const PromptEditor = forwardRef(function PromptEditor(
|
| 835 |
+
{
|
| 836 |
+
selectedProducts,
|
| 837 |
+
prompts,
|
| 838 |
+
onPromptsChange,
|
| 839 |
+
onSaveComplete,
|
| 840 |
+
variant = 'email',
|
| 841 |
+
linkedinPrompts = {},
|
| 842 |
+
onLinkedinPromptsChange,
|
| 843 |
+
includeLinkedinInCampaign = false,
|
| 844 |
+
},
|
| 845 |
+
ref
|
| 846 |
+
) {
|
| 847 |
const [activeTab, setActiveTab] = useState(selectedProducts[0]?.name || '');
|
| 848 |
const [savedStatus, setSavedStatus] = useState({});
|
| 849 |
const [localPrompts, setLocalPrompts] = useState({});
|
|
|
|
| 858 |
}, [selectedProducts, activeTab]);
|
| 859 |
|
| 860 |
useEffect(() => {
|
| 861 |
+
if (variant === 'campaign') {
|
| 862 |
+
const newLocal = {};
|
| 863 |
+
selectedProducts.forEach((product) => {
|
| 864 |
+
const savedEmail = prompts[product.name];
|
| 865 |
+
const savedLi = linkedinPrompts[product.name];
|
| 866 |
+
const emailDefault = DEFAULT_TEMPLATES[product.name];
|
| 867 |
+
const liDefault = LINKEDIN_DEFAULT_TEMPLATES[product.name];
|
| 868 |
+
const emailFallback = `Subject: {{first_name}}, let's talk about ${product.name}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${product.name} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
|
| 869 |
+
const liFallback = `🔒 LINKEDIN SYSTEM PROMPT\n\nGenerate a 3-message LinkedIn sequence for ${product.name}.\nLabel: Message 1, Message 2, Message 3.\nUse {{first_name}}, {{company}}. Sender: Anna.`;
|
| 870 |
+
const email = savedEmail || emailDefault || emailFallback;
|
| 871 |
+
if (includeLinkedinInCampaign) {
|
| 872 |
+
const li = savedLi || liDefault || liFallback;
|
| 873 |
+
newLocal[product.name] = `${email}${CAMPAIGN_COMBINED_PROMPT_SPLIT}${li}`;
|
| 874 |
+
} else {
|
| 875 |
+
newLocal[product.name] = email;
|
| 876 |
+
}
|
| 877 |
+
});
|
| 878 |
+
setLocalPrompts(newLocal);
|
| 879 |
+
return;
|
| 880 |
+
}
|
| 881 |
const library = variant === 'linkedin' ? LINKEDIN_DEFAULT_TEMPLATES : DEFAULT_TEMPLATES;
|
| 882 |
const newPrompts = {};
|
| 883 |
selectedProducts.forEach((product) => {
|
|
|
|
| 895 |
}
|
| 896 |
});
|
| 897 |
setLocalPrompts(newPrompts);
|
| 898 |
+
}, [selectedProducts, prompts, linkedinPrompts, variant, includeLinkedinInCampaign]);
|
| 899 |
|
| 900 |
const handlePromptChange = (productName, value) => {
|
| 901 |
setLocalPrompts(prev => ({
|
|
|
|
| 908 |
}));
|
| 909 |
};
|
| 910 |
|
| 911 |
+
const flushProductToParent = useCallback(
|
| 912 |
+
(productName) => {
|
| 913 |
+
if (variant !== 'campaign') return;
|
| 914 |
+
const raw = localPrompts[productName] ?? '';
|
| 915 |
+
if (!includeLinkedinInCampaign || !onLinkedinPromptsChange) {
|
| 916 |
+
onPromptsChange((prev) => ({ ...prev, [productName]: raw }));
|
| 917 |
+
return;
|
| 918 |
+
}
|
| 919 |
+
const { email, linkedin } = splitCampaignRaw(raw, true);
|
| 920 |
+
onPromptsChange((prev) => ({ ...prev, [productName]: email }));
|
| 921 |
+
onLinkedinPromptsChange((prev) => ({ ...prev, [productName]: linkedin }));
|
| 922 |
+
},
|
| 923 |
+
[variant, localPrompts, includeLinkedinInCampaign, onPromptsChange, onLinkedinPromptsChange]
|
| 924 |
+
);
|
| 925 |
+
|
| 926 |
const handleSave = (productName) => {
|
| 927 |
+
if (variant === 'campaign') {
|
| 928 |
+
flushProductToParent(productName);
|
| 929 |
+
} else {
|
| 930 |
+
onPromptsChange({
|
| 931 |
+
...prompts,
|
| 932 |
+
[productName]: localPrompts[productName] ?? '',
|
| 933 |
+
});
|
| 934 |
+
}
|
| 935 |
setSavedStatus(prev => ({
|
| 936 |
...prev,
|
| 937 |
[productName]: true
|
|
|
|
| 953 |
};
|
| 954 |
|
| 955 |
const handleRestoreDefault = (productName) => {
|
| 956 |
+
if (variant === 'campaign') {
|
| 957 |
+
const emailDefault =
|
| 958 |
+
DEFAULT_TEMPLATES[productName] ||
|
| 959 |
+
`Subject: {{first_name}}, let's talk about ${productName}\n\nHi {{first_name}},\n\nI wanted to reach out about how ${productName} could benefit {{company}}.\n\n[Your personalized message here]\n\nBest,\n{{sender_name}}`;
|
| 960 |
+
if (includeLinkedinInCampaign) {
|
| 961 |
+
const liDefault =
|
| 962 |
+
LINKEDIN_DEFAULT_TEMPLATES[productName] ||
|
| 963 |
+
`🔒 LINKEDIN SYSTEM PROMPT\n\nGenerate a 3-message LinkedIn sequence for ${productName}.\nLabel: Message 1, Message 2, Message 3.\nUse {{first_name}}, {{company}}. Sender: Anna.`;
|
| 964 |
+
handlePromptChange(
|
| 965 |
+
productName,
|
| 966 |
+
`${emailDefault}${CAMPAIGN_COMBINED_PROMPT_SPLIT}${liDefault}`
|
| 967 |
+
);
|
| 968 |
+
} else {
|
| 969 |
+
handlePromptChange(productName, emailDefault);
|
| 970 |
+
}
|
| 971 |
+
return;
|
| 972 |
+
}
|
| 973 |
const library = variant === 'linkedin' ? LINKEDIN_DEFAULT_TEMPLATES : DEFAULT_TEMPLATES;
|
| 974 |
const defaultTemplate =
|
| 975 |
library[productName] ||
|
|
|
|
| 979 |
handlePromptChange(productName, defaultTemplate);
|
| 980 |
};
|
| 981 |
|
| 982 |
+
useImperativeHandle(
|
| 983 |
+
ref,
|
| 984 |
+
() => ({
|
| 985 |
+
commitCampaignPrompts() {
|
| 986 |
+
if (variant !== 'campaign') return;
|
| 987 |
+
const nextP = { ...prompts };
|
| 988 |
+
const nextLi = { ...linkedinPrompts };
|
| 989 |
+
selectedProducts.forEach((product) => {
|
| 990 |
+
const name = product.name;
|
| 991 |
+
const raw = localPrompts[name] ?? '';
|
| 992 |
+
if (!includeLinkedinInCampaign || !onLinkedinPromptsChange) {
|
| 993 |
+
nextP[name] = raw;
|
| 994 |
+
} else {
|
| 995 |
+
const { email, linkedin } = splitCampaignRaw(raw, true);
|
| 996 |
+
nextP[name] = email;
|
| 997 |
+
nextLi[name] = linkedin;
|
| 998 |
+
}
|
| 999 |
+
});
|
| 1000 |
+
onPromptsChange(nextP);
|
| 1001 |
+
if (includeLinkedinInCampaign && onLinkedinPromptsChange) {
|
| 1002 |
+
onLinkedinPromptsChange(nextLi);
|
| 1003 |
+
}
|
| 1004 |
+
},
|
| 1005 |
+
}),
|
| 1006 |
+
[
|
| 1007 |
+
variant,
|
| 1008 |
+
selectedProducts,
|
| 1009 |
+
localPrompts,
|
| 1010 |
+
prompts,
|
| 1011 |
+
linkedinPrompts,
|
| 1012 |
+
includeLinkedinInCampaign,
|
| 1013 |
+
onPromptsChange,
|
| 1014 |
+
onLinkedinPromptsChange,
|
| 1015 |
+
]
|
| 1016 |
+
);
|
| 1017 |
+
|
| 1018 |
if (selectedProducts.length === 0) {
|
| 1019 |
return (
|
| 1020 |
<div className="rounded-2xl border border-slate-200 bg-slate-50/50 p-12 text-center">
|
|
|
|
| 1057 |
<div className="rounded-lg bg-violet-100 p-2">
|
| 1058 |
{variant === 'linkedin' ? (
|
| 1059 |
<Linkedin className="h-4 w-4 text-violet-600" />
|
| 1060 |
+
) : variant === 'campaign' ? (
|
| 1061 |
+
<div className="flex gap-0.5">
|
| 1062 |
+
<Sparkles className="h-4 w-4 text-violet-600" />
|
| 1063 |
+
<Linkedin className="h-4 w-4 text-sky-600" />
|
| 1064 |
+
</div>
|
| 1065 |
) : (
|
| 1066 |
<Sparkles className="h-4 w-4 text-violet-600" />
|
| 1067 |
)}
|
|
|
|
| 1070 |
<h4 className="font-semibold text-slate-800">
|
| 1071 |
{variant === 'linkedin'
|
| 1072 |
? 'LinkedIn sequence prompt'
|
| 1073 |
+
: variant === 'campaign'
|
| 1074 |
+
? includeLinkedinInCampaign
|
| 1075 |
+
? 'Gmail + LinkedIn prompts'
|
| 1076 |
+
: 'Gmail prompts'
|
| 1077 |
+
: 'Email Template'}
|
| 1078 |
</h4>
|
| 1079 |
<p className="text-xs text-slate-500">
|
| 1080 |
+
{variant === 'campaign' && includeLinkedinInCampaign ? (
|
| 1081 |
+
<>
|
| 1082 |
+
Above the line{' '}
|
| 1083 |
+
<code className="rounded bg-slate-100 px-1 text-[10px]">
|
| 1084 |
+
{`<<<CAMPAIGN_LINKEDIN_PROMPT>>>`}
|
| 1085 |
+
</code>{' '}
|
| 1086 |
+
= Gmail (email generation). Below = LinkedIn system prompt.
|
| 1087 |
+
Keep that divider on its own line.
|
| 1088 |
+
</>
|
| 1089 |
+
) : (
|
| 1090 |
+
<>
|
| 1091 |
+
Use variables: {"{{first_name}}"}, {"{{company}}"},{" "}
|
| 1092 |
+
{"{{sender_name}}"}
|
| 1093 |
+
</>
|
| 1094 |
+
)}
|
| 1095 |
</p>
|
| 1096 |
</div>
|
| 1097 |
</div>
|
|
|
|
| 1128 |
<Textarea
|
| 1129 |
value={localPrompts[product.name] || ''}
|
| 1130 |
onChange={(e) => handlePromptChange(product.name, e.target.value)}
|
| 1131 |
+
onBlur={() => {
|
| 1132 |
+
if (variant === 'campaign') {
|
| 1133 |
+
flushProductToParent(product.name);
|
| 1134 |
+
}
|
| 1135 |
+
}}
|
| 1136 |
placeholder={
|
| 1137 |
variant === 'linkedin'
|
| 1138 |
? 'Enter your LinkedIn sequence system prompt…'
|
| 1139 |
+
: variant === 'campaign'
|
| 1140 |
+
? includeLinkedinInCampaign
|
| 1141 |
+
? 'Gmail prompt above the divider, then LinkedIn prompt below…'
|
| 1142 |
+
: 'Enter your Gmail / email generation prompt…'
|
| 1143 |
+
: 'Enter your email template here...'
|
| 1144 |
}
|
| 1145 |
+
className={`${
|
| 1146 |
+
variant === 'campaign' ? 'min-h-[420px]' : 'min-h-[320px]'
|
| 1147 |
+
} font-mono text-sm leading-relaxed resize-none
|
| 1148 |
+
border-slate-200 focus:border-violet-300 focus:ring-violet-200`}
|
| 1149 |
/>
|
| 1150 |
</div>
|
| 1151 |
{/* Save button at bottom for easy access after scrolling */}
|
|
|
|
| 1175 |
</Tabs>
|
| 1176 |
</div>
|
| 1177 |
);
|
| 1178 |
+
});
|
| 1179 |
+
|
| 1180 |
+
export default PromptEditor;
|