| 1 |
import { v as useQueryClient, r as reactExports, u as useQuery, w as useMutation, j as jsxRuntimeExports, bW as KeyRound, S as Sparkles, bF as Pencil, m as BarChart3, X as ExternalLink, aG as Info, bU as Plug, aE as CheckCircle2, b9 as XCircle, by as EyeOff, aR as Eye, aW as Save, E as Loader2, aO as Trash2, bb as Lock, M as MessageCircle, R as RotateCcw, V as AlertTriangle, bd as ChevronUp, y as ChevronDown, C as Crown } from "./react-vendor-CithbthY.js"; |
| 2 |
import { u as useToast, _ as __, I as Input } from "./index-Dec_weLB.js"; |
| 3 |
import { P as PageHeader, C as Card, d as CardContent, f as CardHeader, g as CardTitle, h as CardDescription, B as Button, s as Label, S as Select, c as aiApi } from "../../admin/dist/js/app.js"; |
| 4 |
import { M as ModulePageSkeleton, d as ModuleSectionSkeleton, e as ModuleStatGridSkeleton } from "./module-skeleton-CpoAoviJ.js"; |
| 5 |
const SECTIONS = [ |
| 6 |
{ |
| 7 |
id: "keys", |
| 8 |
label: "API Keys", |
| 9 |
icon: KeyRound, |
| 10 |
description: "Bring your own OpenAI or Anthropic key. Keys are stored encrypted and never leave your server." |
| 11 |
}, |
| 12 |
{ |
| 13 |
id: "voice", |
| 14 |
label: "Brand Voice", |
| 15 |
icon: Sparkles, |
| 16 |
description: "Tone, examples, vocabulary preferences — applied to every generation so AI output sounds like your business." |
| 17 |
}, |
| 18 |
{ |
| 19 |
id: "prompts", |
| 20 |
label: "Prompts", |
| 21 |
icon: Pencil, |
| 22 |
description: "Inspect or edit every system + user prompt the assistant uses. Overrides fall back to defaults per-field." |
| 23 |
}, |
| 24 |
{ |
| 25 |
id: "usage", |
| 26 |
label: "Usage", |
| 27 |
icon: BarChart3, |
| 28 |
description: "Token usage by month, per provider, per feature." |
| 29 |
} |
| 30 |
]; |
| 31 |
const DEFAULT_BRAND_VOICE = { |
| 32 |
tone: "", |
| 33 |
examples: [], |
| 34 |
forbidden: [], |
| 35 |
required: [], |
| 36 |
language: "en-US", |
| 37 |
default_provider: "openai", |
| 38 |
default_model: "" |
| 39 |
}; |
| 40 |
const PlanBadge = ({ tier }) => { |
| 41 |
if (tier === "agency") { |
| 42 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-gradient-to-r from-purple-600 to-indigo-500 px-2.5 py-1 text-xs font-medium text-white shadow-sm", children: [ |
| 43 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Crown, { className: "h-3 w-3" }), |
| 44 |
__("Scale plan", "yatra") |
| 45 |
] }); |
| 46 |
} |
| 47 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-gradient-to-r from-emerald-500 to-teal-500 px-2.5 py-1 text-xs font-medium text-white shadow-sm", children: [ |
| 48 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-3 w-3" }), |
| 49 |
__("Growth plan", "yatra") |
| 50 |
] }); |
| 51 |
}; |
| 52 |
const UpgradeCard = ({ meta }) => { |
| 53 |
const upgradeUrl = (meta == null ? void 0 : meta.upgrade_url) || "https://wpyatra.com/pricing?module=ai-assistant"; |
| 54 |
const licensePageUrl = (meta == null ? void 0 : meta.license_page_url) || "admin.php?page=yatra&subpage=license"; |
| 55 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "max-w-3xl", children: [ |
| 56 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3", children: [ |
| 57 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md bg-blue-100 p-2 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-5 w-5" }) }), |
| 58 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [ |
| 59 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [ |
| 60 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("AI Assistant", "yatra") }), |
| 61 |
/* @__PURE__ */ jsxRuntimeExports.jsx(PlanBadge, { tier: "growth" }) |
| 62 |
] }), |
| 63 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { className: "mt-1", children: __( |
| 64 |
"AI Assistant unlocks on the Growth plan (or Scale). Bring your own OpenAI or Anthropic key — costs are paid directly to your provider, no per-call markup from the plugin.", |
| 65 |
"yatra" |
| 66 |
) }) |
| 67 |
] }) |
| 68 |
] }) }), |
| 69 |
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [ |
| 70 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-600 dark:text-gray-300", children: __( |
| 71 |
"Inline AI affordances on trip descriptions, SEO meta, included/excluded items, FAQs, and more — write less, finish more.", |
| 72 |
"yatra" |
| 73 |
) }), |
| 74 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-3 pt-2", children: [ |
| 75 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("a", { href: upgradeUrl, target: "_blank", rel: "noopener noreferrer", children: [ |
| 76 |
(meta == null ? void 0 : meta.is_pro_active) ? __("Upgrade plan", "yatra") : __("Get Yatra Pro Growth", "yatra"), |
| 77 |
/* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "ml-2 h-4 w-4" }) |
| 78 |
] }) }), |
| 79 |
(meta == null ? void 0 : meta.is_pro_active) && /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { variant: "outline", asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: licensePageUrl, children: __("Manage License", "yatra") }) }) |
| 80 |
] }) |
| 81 |
] }) |
| 82 |
] }); |
| 83 |
}; |
| 84 |
const AiAssistant = () => { |
| 85 |
const { showToast } = useToast(); |
| 86 |
const queryClient = useQueryClient(); |
| 87 |
const [activeSection, setActiveSection] = reactExports.useState("keys"); |
| 88 |
const { data: meta, isLoading: metaLoading } = useQuery({ |
| 89 |
queryKey: ["ai-meta"], |
| 90 |
queryFn: () => aiApi.meta() |
| 91 |
}); |
| 92 |
const { data: bvResp } = useQuery({ |
| 93 |
queryKey: ["ai-brand-voice"], |
| 94 |
queryFn: () => aiApi.getBrandVoice(), |
| 95 |
enabled: Boolean(meta == null ? void 0 : meta.is_ai_eligible) |
| 96 |
}); |
| 97 |
const [bv, setBv] = reactExports.useState(DEFAULT_BRAND_VOICE); |
| 98 |
reactExports.useEffect(() => { |
| 99 |
if (bvResp == null ? void 0 : bvResp.data) { |
| 100 |
setBv({ ...DEFAULT_BRAND_VOICE, ...bvResp.data }); |
| 101 |
} |
| 102 |
}, [bvResp]); |
| 103 |
const saveBvMutation = useMutation({ |
| 104 |
mutationFn: (data) => aiApi.saveBrandVoice(data), |
| 105 |
onSuccess: () => { |
| 106 |
showToast(__("Brand voice saved.", "yatra"), "success"); |
| 107 |
queryClient.invalidateQueries({ queryKey: ["ai-brand-voice"] }); |
| 108 |
}, |
| 109 |
onError: (e) => showToast(extractError(e), "error") |
| 110 |
}); |
| 111 |
const [keyDrafts, setKeyDrafts] = reactExports.useState({}); |
| 112 |
const [showKey, setShowKey] = reactExports.useState({}); |
| 113 |
const setKeyMutation = useMutation({ |
| 114 |
mutationFn: ({ provider, key }) => aiApi.setKey(provider, key), |
| 115 |
onSuccess: (_resp, vars) => { |
| 116 |
showToast(__("API key saved.", "yatra"), "success"); |
| 117 |
setKeyDrafts((d) => ({ ...d, [vars.provider]: "" })); |
| 118 |
queryClient.invalidateQueries({ queryKey: ["ai-meta"] }); |
| 119 |
}, |
| 120 |
onError: (e) => showToast(extractError(e), "error") |
| 121 |
}); |
| 122 |
const deleteKeyMutation = useMutation({ |
| 123 |
mutationFn: (provider) => aiApi.deleteKey(provider), |
| 124 |
onSuccess: () => { |
| 125 |
showToast(__("API key cleared.", "yatra"), "success"); |
| 126 |
queryClient.invalidateQueries({ queryKey: ["ai-meta"] }); |
| 127 |
}, |
| 128 |
onError: (e) => showToast(extractError(e), "error") |
| 129 |
}); |
| 130 |
const testKeyMutation = useMutation({ |
| 131 |
mutationFn: (provider) => aiApi.testKey(provider), |
| 132 |
onSuccess: (resp) => { |
| 133 |
showToast( |
| 134 |
resp.message || __("Connection successful.", "yatra"), |
| 135 |
"success" |
| 136 |
); |
| 137 |
}, |
| 138 |
onError: (e) => showToast(extractError(e), "error") |
| 139 |
}); |
| 140 |
const tripChatMutation = useMutation({ |
| 141 |
mutationFn: (enabled) => aiApi.setTripChatEnabled(enabled), |
| 142 |
onSuccess: (resp) => { |
| 143 |
showToast(resp.message, "success"); |
| 144 |
queryClient.invalidateQueries({ queryKey: ["ai-meta"] }); |
| 145 |
}, |
| 146 |
onError: (e) => showToast(extractError(e), "error") |
| 147 |
}); |
| 148 |
const [chatLimitsDraft, setChatLimitsDraft] = reactExports.useState({ |
| 149 |
per_trip_day: "", |
| 150 |
per_ip_hour: "", |
| 151 |
per_session: "", |
| 152 |
per_session_booking: "", |
| 153 |
max_message_chars: "", |
| 154 |
history_turns: "" |
| 155 |
}); |
| 156 |
reactExports.useEffect(() => { |
| 157 |
if (meta == null ? void 0 : meta.trip_chat_limits) { |
| 158 |
const l = meta.trip_chat_limits; |
| 159 |
setChatLimitsDraft({ |
| 160 |
per_trip_day: String(l.per_trip_day), |
| 161 |
per_ip_hour: String(l.per_ip_hour), |
| 162 |
per_session: String(l.per_session), |
| 163 |
per_session_booking: String(l.per_session_booking), |
| 164 |
max_message_chars: String(l.max_message_chars), |
| 165 |
history_turns: String(l.history_turns) |
| 166 |
}); |
| 167 |
} |
| 168 |
}, [meta == null ? void 0 : meta.trip_chat_limits]); |
| 169 |
const tripChatLimitsMutation = useMutation({ |
| 170 |
mutationFn: (patch) => aiApi.setTripChatLimits(patch), |
| 171 |
onSuccess: (resp) => { |
| 172 |
showToast(resp.message, "success"); |
| 173 |
queryClient.invalidateQueries({ queryKey: ["ai-meta"] }); |
| 174 |
}, |
| 175 |
onError: (e) => showToast(extractError(e), "error") |
| 176 |
}); |
| 177 |
const { data: usageResp } = useQuery({ |
| 178 |
queryKey: ["ai-usage"], |
| 179 |
queryFn: () => aiApi.getUsage(), |
| 180 |
enabled: Boolean(meta == null ? void 0 : meta.is_ai_eligible) && activeSection === "usage" |
| 181 |
}); |
| 182 |
const { data: promptsResp, isLoading: promptsLoading } = useQuery({ |
| 183 |
queryKey: ["ai-prompts"], |
| 184 |
queryFn: () => aiApi.listPrompts(), |
| 185 |
enabled: Boolean(meta == null ? void 0 : meta.is_ai_eligible) && activeSection === "prompts" |
| 186 |
}); |
| 187 |
const [expandedPrompt, setExpandedPrompt] = reactExports.useState(null); |
| 188 |
const [promptDrafts, setPromptDrafts] = reactExports.useState({}); |
| 189 |
reactExports.useEffect(() => { |
| 190 |
if (!(promptsResp == null ? void 0 : promptsResp.data)) return; |
| 191 |
const next = {}; |
| 192 |
for (const row of promptsResp.data) { |
| 193 |
const effSystem = row.override.system && row.override.system !== "" ? row.override.system : row.default.system; |
| 194 |
const effUser = row.override.user && row.override.user !== "" ? row.override.user : row.default.user; |
| 195 |
const effMaxTokens = row.override.max_tokens != null ? String(row.override.max_tokens) : String(row.default.max_tokens); |
| 196 |
const effTemperature = row.override.temperature != null ? String(row.override.temperature) : String(row.default.temperature); |
| 197 |
next[row.task] = { |
| 198 |
system: effSystem, |
| 199 |
user: effUser, |
| 200 |
max_tokens: effMaxTokens, |
| 201 |
temperature: effTemperature |
| 202 |
}; |
| 203 |
} |
| 204 |
setPromptDrafts(next); |
| 205 |
}, [promptsResp]); |
| 206 |
const savePromptMutation = useMutation({ |
| 207 |
mutationFn: ({ |
| 208 |
task, |
| 209 |
override |
| 210 |
}) => aiApi.savePromptOverride(task, override), |
| 211 |
onSuccess: () => { |
| 212 |
showToast(__("Prompt saved.", "yatra"), "success"); |
| 213 |
queryClient.invalidateQueries({ queryKey: ["ai-prompts"] }); |
| 214 |
}, |
| 215 |
onError: (e) => showToast(extractError(e), "error") |
| 216 |
}); |
| 217 |
const resetPromptMutation = useMutation({ |
| 218 |
mutationFn: (task) => aiApi.resetPrompt(task), |
| 219 |
onSuccess: () => { |
| 220 |
showToast(__("Prompt reset to default.", "yatra"), "success"); |
| 221 |
queryClient.invalidateQueries({ queryKey: ["ai-prompts"] }); |
| 222 |
}, |
| 223 |
onError: (e) => showToast(extractError(e), "error") |
| 224 |
}); |
| 225 |
const resetAllPromptsMutation = useMutation({ |
| 226 |
mutationFn: () => aiApi.resetAllPrompts(), |
| 227 |
onSuccess: () => { |
| 228 |
showToast(__("All prompts reset to defaults.", "yatra"), "success"); |
| 229 |
queryClient.invalidateQueries({ queryKey: ["ai-prompts"] }); |
| 230 |
}, |
| 231 |
onError: (e) => showToast(extractError(e), "error") |
| 232 |
}); |
| 233 |
if (metaLoading) { |
| 234 |
return /* @__PURE__ */ jsxRuntimeExports.jsx(ModulePageSkeleton, { variant: "tabs" }); |
| 235 |
} |
| 236 |
if (!meta || !meta.is_ai_eligible) { |
| 237 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ |
| 238 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 239 |
PageHeader, |
| 240 |
{ |
| 241 |
description: __( |
| 242 |
"Inline AI generation for trip content, SEO, and more.", |
| 243 |
"yatra" |
| 244 |
) |
| 245 |
} |
| 246 |
), |
| 247 |
/* @__PURE__ */ jsxRuntimeExports.jsx(UpgradeCard, { meta }) |
| 248 |
] }); |
| 249 |
} |
| 250 |
const moduleEnabled = meta.is_module_enabled; |
| 251 |
const updateBv = (key, value) => setBv((prev) => ({ ...prev, [key]: value })); |
| 252 |
const updateBvList = (key, raw) => { |
| 253 |
const list = raw.split(/\r?\n/).map((s) => s.trim()).filter((s) => s !== ""); |
| 254 |
setBv((prev) => ({ ...prev, [key]: list })); |
| 255 |
}; |
| 256 |
const submitBv = (e) => { |
| 257 |
e.preventDefault(); |
| 258 |
saveBvMutation.mutate(bv); |
| 259 |
}; |
| 260 |
const providerDocs = { |
| 261 |
openai: { |
| 262 |
whyDescription: __( |
| 263 |
"Authenticates every request the plugin sends to OpenAI on your behalf. OpenAI bills your account directly per request — the plugin adds no markup. Without a key, the AI sparkle affordances on Trip / SEO / Email editors stay disabled.", |
| 264 |
"yatra" |
| 265 |
), |
| 266 |
whereToFind: __( |
| 267 |
"Sign in at platform.openai.com → API keys → Create new secret key. Copy the value (starts with sk-…) immediately — OpenAI shows it only once.", |
| 268 |
"yatra" |
| 269 |
), |
| 270 |
docsUrl: "https://platform.openai.com/api-keys", |
| 271 |
pricingUrl: "https://openai.com/api/pricing/" |
| 272 |
}, |
| 273 |
anthropic: { |
| 274 |
whyDescription: __( |
| 275 |
"Authenticates requests to Anthropic's Claude API. Anthropic bills your account directly per request — no plugin markup. Used for the same in-editor sparkle features OpenAI handles; pick whichever provider your team prefers.", |
| 276 |
"yatra" |
| 277 |
), |
| 278 |
whereToFind: __( |
| 279 |
"Sign in at console.anthropic.com → Settings → API Keys → Create Key. Copy the value (starts with sk-ant-…) immediately — Anthropic shows it only once.", |
| 280 |
"yatra" |
| 281 |
), |
| 282 |
docsUrl: "https://console.anthropic.com/settings/keys", |
| 283 |
pricingUrl: "https://www.anthropic.com/pricing#anthropic-api" |
| 284 |
} |
| 285 |
}; |
| 286 |
const renderKeys = () => /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [ |
| 287 |
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [ |
| 288 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("API Keys", "yatra") }), |
| 289 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __( |
| 290 |
"Keys are encrypted at rest with libsodium and never sent to the browser. Generation requests are made from your server directly to your chosen provider — no third-party proxy is involved.", |
| 291 |
"yatra" |
| 292 |
) }) |
| 293 |
] }), |
| 294 |
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "space-y-4", children: [ |
| 295 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-blue-200 bg-blue-50/40 p-3 text-sm dark:border-blue-700 dark:bg-blue-900/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [ |
| 296 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Info, { className: "mt-0.5 h-4 w-4 shrink-0 text-blue-600 dark:text-blue-400" }), |
| 297 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 text-blue-900 dark:text-blue-200", children: [ |
| 298 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium", children: __("How AI Assistant billing works", "yatra") }), |
| 299 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs", children: __( |
| 300 |
"You bring your own OpenAI or Anthropic key. Each AI generation (trip description, FAQ, email body, etc.) is a single API call your provider charges you for — usually fractions of a cent. The plugin never proxies traffic and adds no markup. You only need one key to start; you can save both and pick a default per task under the Defaults tab.", |
| 301 |
"yatra" |
| 302 |
) }), |
| 303 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] opacity-80", children: __( |
| 304 |
'Click "Where to find this" next to a provider for the exact steps in their dashboard.', |
| 305 |
"yatra" |
| 306 |
) }) |
| 307 |
] }) |
| 308 |
] }) }), |
| 309 |
meta.providers.map((p) => { |
| 310 |
const status = meta.keys[p.id] ?? { configured: false, hint: "" }; |
| 311 |
const draft = keyDrafts[p.id] ?? ""; |
| 312 |
const visible = Boolean(showKey[p.id]); |
| 313 |
const docs = providerDocs[p.id]; |
| 314 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 315 |
"div", |
| 316 |
{ |
| 317 |
className: "rounded-md border border-gray-200 p-3 dark:border-gray-700", |
| 318 |
children: [ |
| 319 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [ |
| 320 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ |
| 321 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Plug, { className: "h-4 w-4 text-gray-500" }), |
| 322 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-gray-900 dark:text-white", children: p.label }), |
| 323 |
status.configured ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-green-50 px-2 py-0.5 text-[11px] font-medium text-green-700 dark:bg-green-900/30 dark:text-green-300", children: [ |
| 324 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-3 w-3" }), |
| 325 |
__("Configured", "yatra") |
| 326 |
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600 dark:bg-gray-800 dark:text-gray-300", children: [ |
| 327 |
/* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-3 w-3" }), |
| 328 |
__("Not configured", "yatra") |
| 329 |
] }) |
| 330 |
] }), |
| 331 |
status.configured && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-gray-500", children: status.hint }) |
| 332 |
] }), |
| 333 |
docs && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3 space-y-2 rounded-md border border-gray-100 bg-gray-50/60 p-3 text-xs text-gray-700 dark:border-gray-700 dark:bg-gray-800/40 dark:text-gray-300", children: [ |
| 334 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-1.5", children: [ |
| 335 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Info, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-gray-500" }), |
| 336 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 337 |
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-gray-900 dark:text-white", children: __("Why it's needed:", "yatra") }), |
| 338 |
" ", |
| 339 |
docs.whyDescription |
| 340 |
] }) |
| 341 |
] }), |
| 342 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-1.5", children: [ |
| 343 |
/* @__PURE__ */ jsxRuntimeExports.jsx(KeyRound, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-gray-500" }), |
| 344 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 345 |
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-gray-900 dark:text-white", children: __("Where to find it:", "yatra") }), |
| 346 |
" ", |
| 347 |
docs.whereToFind |
| 348 |
] }) |
| 349 |
] }), |
| 350 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap gap-2 pt-1", children: [ |
| 351 |
/* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 352 |
"a", |
| 353 |
{ |
| 354 |
href: docs.docsUrl, |
| 355 |
target: "_blank", |
| 356 |
rel: "noopener noreferrer", |
| 357 |
className: "inline-flex items-center gap-1 rounded border border-gray-300 px-2 py-0.5 text-[11px] text-gray-700 hover:bg-white dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-800", |
| 358 |
children: [ |
| 359 |
__("Open API keys page", "yatra"), |
| 360 |
/* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "h-3 w-3" }) |
| 361 |
] |
| 362 |
} |
| 363 |
), |
| 364 |
/* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 365 |
"a", |
| 366 |
{ |
| 367 |
href: docs.pricingUrl, |
| 368 |
target: "_blank", |
| 369 |
rel: "noopener noreferrer", |
| 370 |
className: "inline-flex items-center gap-1 rounded border border-gray-300 px-2 py-0.5 text-[11px] text-gray-700 hover:bg-white dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-800", |
| 371 |
children: [ |
| 372 |
__("Pricing", "yatra"), |
| 373 |
/* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "h-3 w-3" }) |
| 374 |
] |
| 375 |
} |
| 376 |
) |
| 377 |
] }) |
| 378 |
] }), |
| 379 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [ |
| 380 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-[240px]", children: [ |
| 381 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: `ai-key-${p.id}`, children: __("API key", "yatra") }), |
| 382 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative mt-1", children: [ |
| 383 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 384 |
Input, |
| 385 |
{ |
| 386 |
id: `ai-key-${p.id}`, |
| 387 |
type: visible ? "text" : "password", |
| 388 |
value: draft, |
| 389 |
placeholder: status.configured ? __("Enter a new key to replace…", "yatra") : __("sk-… or sk-ant-…", "yatra"), |
| 390 |
autoComplete: "off", |
| 391 |
onChange: (e) => setKeyDrafts((d) => ({ |
| 392 |
...d, |
| 393 |
[p.id]: e.target.value |
| 394 |
})) |
| 395 |
} |
| 396 |
), |
| 397 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 398 |
"button", |
| 399 |
{ |
| 400 |
type: "button", |
| 401 |
onClick: () => setShowKey((s) => ({ ...s, [p.id]: !s[p.id] })), |
| 402 |
className: "absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600", |
| 403 |
"aria-label": visible ? __("Hide key", "yatra") : __("Show key", "yatra"), |
| 404 |
children: visible ? /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "h-4 w-4" }) |
| 405 |
} |
| 406 |
) |
| 407 |
] }) |
| 408 |
] }), |
| 409 |
/* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 410 |
Button, |
| 411 |
{ |
| 412 |
type: "button", |
| 413 |
onClick: () => setKeyMutation.mutate({ provider: p.id, key: draft }), |
| 414 |
disabled: draft.trim() === "" || setKeyMutation.isPending, |
| 415 |
children: [ |
| 416 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "mr-2 h-4 w-4" }), |
| 417 |
__("Save", "yatra") |
| 418 |
] |
| 419 |
} |
| 420 |
), |
| 421 |
status.configured && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 422 |
/* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 423 |
Button, |
| 424 |
{ |
| 425 |
type: "button", |
| 426 |
variant: "outline", |
| 427 |
onClick: () => testKeyMutation.mutate(p.id), |
| 428 |
disabled: testKeyMutation.isPending, |
| 429 |
children: [ |
| 430 |
testKeyMutation.isPending && testKeyMutation.variables === p.id ? /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, |
| 431 |
__("Test connection", "yatra") |
| 432 |
] |
| 433 |
} |
| 434 |
), |
| 435 |
/* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 436 |
Button, |
| 437 |
{ |
| 438 |
type: "button", |
| 439 |
variant: "ghost", |
| 440 |
onClick: () => deleteKeyMutation.mutate(p.id), |
| 441 |
disabled: deleteKeyMutation.isPending, |
| 442 |
children: [ |
| 443 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "mr-2 h-4 w-4" }), |
| 444 |
__("Clear", "yatra") |
| 445 |
] |
| 446 |
} |
| 447 |
) |
| 448 |
] }) |
| 449 |
] }) |
| 450 |
] |
| 451 |
}, |
| 452 |
p.id |
| 453 |
); |
| 454 |
}), |
| 455 |
!moduleEnabled && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50/40 p-3 text-xs text-amber-800 dark:border-amber-500/40 dark:bg-amber-900/20 dark:text-amber-200", children: [ |
| 456 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Lock, { className: "mt-0.5 h-4 w-4 shrink-0" }), |
| 457 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 458 |
__( |
| 459 |
"AI Assistant module is not enabled yet. Once a key is saved, enable the module from the Modules page so sparkle affordances appear in the editors.", |
| 460 |
"yatra" |
| 461 |
), |
| 462 |
" ", |
| 463 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 464 |
"a", |
| 465 |
{ |
| 466 |
className: "font-medium underline", |
| 467 |
href: "admin.php?page=yatra&subpage=modules", |
| 468 |
children: __("Go to Modules →", "yatra") |
| 469 |
} |
| 470 |
) |
| 471 |
] }) |
| 472 |
] }), |
| 473 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-gray-200 p-3 dark:border-gray-700", children: [ |
| 474 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-3", children: [ |
| 475 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [ |
| 476 |
/* @__PURE__ */ jsxRuntimeExports.jsx(MessageCircle, { className: "mt-0.5 h-4 w-4 text-blue-600 dark:text-blue-300" }), |
| 477 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 478 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium text-gray-900 dark:text-white", children: __("Public Chat Widget", "yatra") }), |
| 479 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 text-xs text-gray-500 dark:text-gray-400", children: __( |
| 480 |
`Adds a "Chat with AI about this trip" button below the Send Enquiry button on every single-trip page. Visitors can ask trip-specific questions. Rate-limited per IP/session/trip/day so a hostile visitor can't burn your provider budget.`, |
| 481 |
"yatra" |
| 482 |
) }) |
| 483 |
] }) |
| 484 |
] }), |
| 485 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 486 |
"button", |
| 487 |
{ |
| 488 |
type: "button", |
| 489 |
role: "switch", |
| 490 |
"aria-checked": Boolean(meta.trip_chat_enabled), |
| 491 |
onClick: () => tripChatMutation.mutate(!meta.trip_chat_enabled), |
| 492 |
disabled: tripChatMutation.isPending || !moduleEnabled, |
| 493 |
className: `relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full transition-colors ${meta.trip_chat_enabled ? "bg-blue-600" : "bg-gray-300 dark:bg-gray-600"} ${tripChatMutation.isPending || !moduleEnabled ? "opacity-50 cursor-not-allowed" : ""}`, |
| 494 |
children: /* @__PURE__ */ jsxRuntimeExports.jsx( |
| 495 |
"span", |
| 496 |
{ |
| 497 |
className: `inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${meta.trip_chat_enabled ? "translate-x-5" : "translate-x-0.5"} translate-y-0.5` |
| 498 |
} |
| 499 |
) |
| 500 |
} |
| 501 |
) |
| 502 |
] }), |
| 503 |
!moduleEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-[11px] text-amber-600 dark:text-amber-300", children: __( |
| 504 |
"Enable the AI Assistant module first, then turn this on.", |
| 505 |
"yatra" |
| 506 |
) }), |
| 507 |
meta.trip_chat_enabled && meta.trip_chat_limits_schema && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 space-y-3 border-t border-gray-200 pt-3 dark:border-gray-700", children: [ |
| 508 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 509 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-medium text-gray-700 dark:text-gray-200", children: __("Chat rate limits", "yatra") }), |
| 510 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 text-[11px] text-gray-500 dark:text-gray-400", children: __( |
| 511 |
"Caps on the public chat widget to protect your AI provider budget. Higher numbers = more freedom for visitors and more potential cost. Each field is clamped to a safe range on save.", |
| 512 |
"yatra" |
| 513 |
) }) |
| 514 |
] }), |
| 515 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: [ |
| 516 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 517 |
LimitField, |
| 518 |
{ |
| 519 |
label: __("Per trip · per day", "yatra"), |
| 520 |
hint: __("Max chat messages per trip per day.", "yatra"), |
| 521 |
field: "per_trip_day", |
| 522 |
draft: chatLimitsDraft, |
| 523 |
setDraft: setChatLimitsDraft, |
| 524 |
schema: meta.trip_chat_limits_schema |
| 525 |
} |
| 526 |
), |
| 527 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 528 |
LimitField, |
| 529 |
{ |
| 530 |
label: __("Per IP · per hour", "yatra"), |
| 531 |
hint: __( |
| 532 |
"Max messages from one visitor IP per hour.", |
| 533 |
"yatra" |
| 534 |
), |
| 535 |
field: "per_ip_hour", |
| 536 |
draft: chatLimitsDraft, |
| 537 |
setDraft: setChatLimitsDraft, |
| 538 |
schema: meta.trip_chat_limits_schema |
| 539 |
} |
| 540 |
), |
| 541 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 542 |
LimitField, |
| 543 |
{ |
| 544 |
label: __("Per session (info)", "yatra"), |
| 545 |
hint: __( |
| 546 |
"Cap when visitor is just asking questions.", |
| 547 |
"yatra" |
| 548 |
), |
| 549 |
field: "per_session", |
| 550 |
draft: chatLimitsDraft, |
| 551 |
setDraft: setChatLimitsDraft, |
| 552 |
schema: meta.trip_chat_limits_schema |
| 553 |
} |
| 554 |
), |
| 555 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 556 |
LimitField, |
| 557 |
{ |
| 558 |
label: __("Per session (booking)", "yatra"), |
| 559 |
hint: __( |
| 560 |
"Higher cap once visitor signals booking intent.", |
| 561 |
"yatra" |
| 562 |
), |
| 563 |
field: "per_session_booking", |
| 564 |
draft: chatLimitsDraft, |
| 565 |
setDraft: setChatLimitsDraft, |
| 566 |
schema: meta.trip_chat_limits_schema |
| 567 |
} |
| 568 |
), |
| 569 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 570 |
LimitField, |
| 571 |
{ |
| 572 |
label: __("Max message length", "yatra"), |
| 573 |
hint: __( |
| 574 |
"Max characters in a single visitor message.", |
| 575 |
"yatra" |
| 576 |
), |
| 577 |
field: "max_message_chars", |
| 578 |
draft: chatLimitsDraft, |
| 579 |
setDraft: setChatLimitsDraft, |
| 580 |
schema: meta.trip_chat_limits_schema |
| 581 |
} |
| 582 |
), |
| 583 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 584 |
LimitField, |
| 585 |
{ |
| 586 |
label: __("History turns sent", "yatra"), |
| 587 |
hint: __( |
| 588 |
"How many recent turns the AI sees for context.", |
| 589 |
"yatra" |
| 590 |
), |
| 591 |
field: "history_turns", |
| 592 |
draft: chatLimitsDraft, |
| 593 |
setDraft: setChatLimitsDraft, |
| 594 |
schema: meta.trip_chat_limits_schema |
| 595 |
} |
| 596 |
) |
| 597 |
] }), |
| 598 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2", children: [ |
| 599 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 600 |
Button, |
| 601 |
{ |
| 602 |
type: "button", |
| 603 |
variant: "outline", |
| 604 |
onClick: () => { |
| 605 |
if (meta.trip_chat_limits) { |
| 606 |
const l = meta.trip_chat_limits; |
| 607 |
setChatLimitsDraft({ |
| 608 |
per_trip_day: String(l.per_trip_day), |
| 609 |
per_ip_hour: String(l.per_ip_hour), |
| 610 |
per_session: String(l.per_session), |
| 611 |
per_session_booking: String(l.per_session_booking), |
| 612 |
max_message_chars: String(l.max_message_chars), |
| 613 |
history_turns: String(l.history_turns) |
| 614 |
}); |
| 615 |
} |
| 616 |
}, |
| 617 |
disabled: tripChatLimitsMutation.isPending, |
| 618 |
children: __("Revert", "yatra") |
| 619 |
} |
| 620 |
), |
| 621 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 622 |
Button, |
| 623 |
{ |
| 624 |
type: "button", |
| 625 |
onClick: () => { |
| 626 |
const patch = {}; |
| 627 |
Object.keys(chatLimitsDraft).forEach((k) => { |
| 628 |
const v = parseInt(chatLimitsDraft[k], 10); |
| 629 |
if (Number.isFinite(v)) patch[k] = v; |
| 630 |
}); |
| 631 |
tripChatLimitsMutation.mutate(patch); |
| 632 |
}, |
| 633 |
disabled: tripChatLimitsMutation.isPending, |
| 634 |
children: tripChatLimitsMutation.isPending ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 635 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), |
| 636 |
__("Saving…", "yatra") |
| 637 |
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 638 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "mr-2 h-4 w-4" }), |
| 639 |
__("Save limits", "yatra") |
| 640 |
] }) |
| 641 |
} |
| 642 |
) |
| 643 |
] }) |
| 644 |
] }) |
| 645 |
] }) |
| 646 |
] }) |
| 647 |
] }); |
| 648 |
const renderVoice = () => { |
| 649 |
var _a; |
| 650 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [ |
| 651 |
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [ |
| 652 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("Brand voice", "yatra") }), |
| 653 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __( |
| 654 |
"Define your voice once — it flows into every generation across trips, SEO, and emails. The more specific, the more your AI output sounds like you.", |
| 655 |
"yatra" |
| 656 |
) }) |
| 657 |
] }), |
| 658 |
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { children: [ |
| 659 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 rounded-md border border-blue-200 bg-blue-50/60 p-3 text-xs text-blue-900 dark:border-blue-500/40 dark:bg-blue-900/20 dark:text-blue-200", children: [ |
| 660 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1 font-semibold", children: __("How brand voice is used", "yatra") }), |
| 661 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "leading-relaxed", children: __( |
| 662 |
"Everything you fill in here is automatically prepended to every prompt the plugin sends to your AI provider. Trip descriptions, enquiry replies, email templates, the public chat widget, and the back-office wizards all start with these instructions — so your output sounds like one consistent brand, not a different voice per feature.", |
| 663 |
"yatra" |
| 664 |
) }) |
| 665 |
] }), |
| 666 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: submitBv, className: "space-y-5", children: [ |
| 667 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 668 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "bv-tone", children: __("Tone description", "yatra") }), |
| 669 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-1.5 text-xs text-gray-500 dark:text-gray-400", children: __( |
| 670 |
"The single most important field. 2-3 sentences describing how you'd describe your brand to a new copywriter. Plain English — not marketing jargon. AI reads this every generation and aims for this tone.", |
| 671 |
"yatra" |
| 672 |
) }), |
| 673 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 674 |
"textarea", |
| 675 |
{ |
| 676 |
id: "bv-tone", |
| 677 |
rows: 3, |
| 678 |
className: "mt-1 w-full rounded-md border border-gray-300 bg-white p-2 text-sm dark:border-gray-600 dark:bg-gray-800", |
| 679 |
value: bv.tone, |
| 680 |
placeholder: __( |
| 681 |
"e.g. Warm, adventurous, never corporate. Speak directly to the traveler. We're a small team, not a corporation.", |
| 682 |
"yatra" |
| 683 |
), |
| 684 |
onChange: (e) => updateBv("tone", e.target.value) |
| 685 |
} |
| 686 |
) |
| 687 |
] }), |
| 688 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 689 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "bv-examples", children: __("Style examples (one paragraph per line)", "yatra") }), |
| 690 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-1.5 text-xs text-gray-500 dark:text-gray-400", children: __( |
| 691 |
"Paste 3-5 paragraphs from your existing trip pages, emails, or marketing copy that sound right. AI uses these as few-shot examples — concrete writing samples beat abstract tone descriptions for getting the voice exactly right. One paragraph per line.", |
| 692 |
"yatra" |
| 693 |
) }), |
| 694 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 695 |
"textarea", |
| 696 |
{ |
| 697 |
id: "bv-examples", |
| 698 |
rows: 6, |
| 699 |
className: "mt-1 w-full rounded-md border border-gray-300 bg-white p-2 font-mono text-xs dark:border-gray-600 dark:bg-gray-800", |
| 700 |
value: bv.examples.join("\n"), |
| 701 |
placeholder: __( |
| 702 |
"Paste 3-5 paragraphs from your existing trips/emails that sound like you. AI uses these as few-shot examples.", |
| 703 |
"yatra" |
| 704 |
), |
| 705 |
onChange: (e) => updateBvList("examples", e.target.value) |
| 706 |
} |
| 707 |
) |
| 708 |
] }), |
| 709 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [ |
| 710 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 711 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "bv-forbidden", children: __("Forbidden words / phrases", "yatra") }), |
| 712 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-1.5 text-xs text-gray-500 dark:text-gray-400", children: __( |
| 713 |
'Words AI must never use. Common entries: "unforgettable", "once in a lifetime", "breathtaking", "world-class", or any competitor name. One per line — AI treats these as a hard blocklist.', |
| 714 |
"yatra" |
| 715 |
) }), |
| 716 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 717 |
"textarea", |
| 718 |
{ |
| 719 |
id: "bv-forbidden", |
| 720 |
rows: 4, |
| 721 |
className: "mt-1 w-full rounded-md border border-gray-300 bg-white p-2 text-xs dark:border-gray-600 dark:bg-gray-800", |
| 722 |
value: bv.forbidden.join("\n"), |
| 723 |
placeholder: __( |
| 724 |
"One per line — words / phrases to avoid.", |
| 725 |
"yatra" |
| 726 |
), |
| 727 |
onChange: (e) => updateBvList("forbidden", e.target.value) |
| 728 |
} |
| 729 |
) |
| 730 |
] }), |
| 731 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 732 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "bv-required", children: __("Required mentions", "yatra") }), |
| 733 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-1.5 text-xs text-gray-500 dark:text-gray-400", children: __( |
| 734 |
'Things AI should weave in WHEN RELEVANT — never forced. Examples: "local guides", "small group sizes", "sustainable practices", "our 24/7 support". One per line. AI mentions these only when they fit the context naturally.', |
| 735 |
"yatra" |
| 736 |
) }), |
| 737 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 738 |
"textarea", |
| 739 |
{ |
| 740 |
id: "bv-required", |
| 741 |
rows: 4, |
| 742 |
className: "mt-1 w-full rounded-md border border-gray-300 bg-white p-2 text-xs dark:border-gray-600 dark:bg-gray-800", |
| 743 |
value: bv.required.join("\n"), |
| 744 |
placeholder: __( |
| 745 |
"One per line — things to weave in when relevant.", |
| 746 |
"yatra" |
| 747 |
), |
| 748 |
onChange: (e) => updateBvList("required", e.target.value) |
| 749 |
} |
| 750 |
) |
| 751 |
] }) |
| 752 |
] }), |
| 753 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-4 md:grid-cols-3", children: [ |
| 754 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 755 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "bv-lang", children: __("Default language", "yatra") }), |
| 756 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-1.5 text-xs text-gray-500 dark:text-gray-400", children: __( |
| 757 |
"BCP-47 code. AI writes in this language unless overridden by per-prompt context (e.g. enquiry replies match the customer's preferred language when known).", |
| 758 |
"yatra" |
| 759 |
) }), |
| 760 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 761 |
Input, |
| 762 |
{ |
| 763 |
id: "bv-lang", |
| 764 |
value: bv.language, |
| 765 |
placeholder: "en-US", |
| 766 |
onChange: (e) => updateBv("language", e.target.value) |
| 767 |
} |
| 768 |
) |
| 769 |
] }), |
| 770 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 771 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "bv-provider", children: __("Default provider", "yatra") }), |
| 772 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-1.5 text-xs text-gray-500 dark:text-gray-400", children: __( |
| 773 |
"Which AI service handles each generation by default. Per-feature usage runs through the provider whose API key is configured under API Keys above.", |
| 774 |
"yatra" |
| 775 |
) }), |
| 776 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 777 |
Select, |
| 778 |
{ |
| 779 |
id: "bv-provider", |
| 780 |
value: bv.default_provider, |
| 781 |
onChange: (e) => updateBv("default_provider", e.target.value), |
| 782 |
children: meta.providers.map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: p.id, children: p.label }, p.id)) |
| 783 |
} |
| 784 |
) |
| 785 |
] }), |
| 786 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 787 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "bv-model", children: __("Default model", "yatra") }), |
| 788 |
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mb-1.5 text-xs text-gray-500 dark:text-gray-400", children: __( |
| 789 |
"Specific model within the provider. Smaller / faster models (gpt-4o-mini, Claude Haiku) are cheaper and ~good enough for most generations. Larger models are slower + costlier but produce richer output for trip descriptions.", |
| 790 |
"yatra" |
| 791 |
) }), |
| 792 |
/* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 793 |
Select, |
| 794 |
{ |
| 795 |
id: "bv-model", |
| 796 |
value: bv.default_model, |
| 797 |
onChange: (e) => updateBv("default_model", e.target.value), |
| 798 |
children: [ |
| 799 |
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: __("(Provider default)", "yatra") }), |
| 800 |
(((_a = meta.providers.find((p) => p.id === bv.default_provider)) == null ? void 0 : _a.models) ?? []).map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: m.id, children: m.label }, m.id)) |
| 801 |
] |
| 802 |
} |
| 803 |
) |
| 804 |
] }) |
| 805 |
] }), |
| 806 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-3 pt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "submit", disabled: saveBvMutation.isPending, children: saveBvMutation.isPending ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 807 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), |
| 808 |
__("Saving…", "yatra") |
| 809 |
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 810 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "mr-2 h-4 w-4" }), |
| 811 |
__("Save brand voice", "yatra") |
| 812 |
] }) }) }) |
| 813 |
] }) |
| 814 |
] }) |
| 815 |
] }); |
| 816 |
}; |
| 817 |
const renderUsage = () => { |
| 818 |
const usage = usageResp == null ? void 0 : usageResp.data; |
| 819 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [ |
| 820 |
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { children: [ |
| 821 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("Usage", "yatra") }), |
| 822 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __( |
| 823 |
"Rolling 13-month token totals, per provider, per task. Costs are paid directly to your AI provider — no markup is added.", |
| 824 |
"yatra" |
| 825 |
) }) |
| 826 |
] }), |
| 827 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: !usage ? /* @__PURE__ */ jsxRuntimeExports.jsx(ModuleStatGridSkeleton, { tiles: 3 }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 828 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4 grid grid-cols-3 gap-3", children: [ |
| 829 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 830 |
Stat, |
| 831 |
{ |
| 832 |
label: __("Total calls", "yatra"), |
| 833 |
value: usage.totals.calls.toLocaleString() |
| 834 |
} |
| 835 |
), |
| 836 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 837 |
Stat, |
| 838 |
{ |
| 839 |
label: __("Prompt tokens", "yatra"), |
| 840 |
value: usage.totals.prompt.toLocaleString() |
| 841 |
} |
| 842 |
), |
| 843 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 844 |
Stat, |
| 845 |
{ |
| 846 |
label: __("Completion tokens", "yatra"), |
| 847 |
value: usage.totals.completion.toLocaleString() |
| 848 |
} |
| 849 |
) |
| 850 |
] }), |
| 851 |
Object.keys(usage.months).length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: __("No AI usage yet.", "yatra") }), |
| 852 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: Object.entries(usage.months).map(([month, providers]) => /* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 853 |
"div", |
| 854 |
{ |
| 855 |
className: "rounded-md border border-gray-200 p-3 dark:border-gray-700", |
| 856 |
children: [ |
| 857 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2 text-sm font-medium text-gray-900 dark:text-white", children: [ |
| 858 |
month, |
| 859 |
month === usage.current_month && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-2 rounded-full bg-blue-100 px-2 py-0.5 text-[10px] font-semibold uppercase text-blue-700 dark:bg-blue-900/30 dark:text-blue-300", children: __("Current", "yatra") }) |
| 860 |
] }), |
| 861 |
Object.entries(providers).map(([prov, bucket]) => /* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 862 |
"div", |
| 863 |
{ |
| 864 |
className: "mb-1 grid grid-cols-4 gap-2 text-xs", |
| 865 |
children: [ |
| 866 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-gray-700 dark:text-gray-200", children: prov }), |
| 867 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 868 |
bucket.calls, |
| 869 |
" ", |
| 870 |
__("calls", "yatra") |
| 871 |
] }), |
| 872 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 873 |
bucket.prompt.toLocaleString(), |
| 874 |
" ", |
| 875 |
__("in", "yatra") |
| 876 |
] }), |
| 877 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 878 |
bucket.completion.toLocaleString(), |
| 879 |
" ", |
| 880 |
__("out", "yatra") |
| 881 |
] }) |
| 882 |
] |
| 883 |
}, |
| 884 |
prov |
| 885 |
)) |
| 886 |
] |
| 887 |
}, |
| 888 |
month |
| 889 |
)) }) |
| 890 |
] }) }) |
| 891 |
] }); |
| 892 |
}; |
| 893 |
const renderPrompts = () => { |
| 894 |
var _a; |
| 895 |
const rows = (promptsResp == null ? void 0 : promptsResp.data) ?? []; |
| 896 |
const grouped = {}; |
| 897 |
for (const r of rows) { |
| 898 |
(grouped[_a = r.category] || (grouped[_a] = [])).push(r); |
| 899 |
} |
| 900 |
const groupOrder = Object.keys(grouped); |
| 901 |
const submitPrompt = (row) => { |
| 902 |
const draft = promptDrafts[row.task]; |
| 903 |
if (!draft) return; |
| 904 |
const override = {}; |
| 905 |
const sysTrim = draft.system.trim(); |
| 906 |
override.system = sysTrim === "" || sysTrim === row.default.system.trim() ? "" : draft.system; |
| 907 |
const usrTrim = draft.user.trim(); |
| 908 |
override.user = usrTrim === "" || usrTrim === row.default.user.trim() ? "" : draft.user; |
| 909 |
const mt = parseInt(draft.max_tokens, 10); |
| 910 |
override.max_tokens = Number.isFinite(mt) && mt !== row.default.max_tokens ? mt : null; |
| 911 |
const tp = parseFloat(draft.temperature); |
| 912 |
override.temperature = Number.isFinite(tp) && tp !== row.default.temperature ? tp : null; |
| 913 |
savePromptMutation.mutate({ task: row.task, override }); |
| 914 |
}; |
| 915 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { children: [ |
| 916 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [ |
| 917 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 918 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { children: __("Prompts", "yatra") }), |
| 919 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: __( |
| 920 |
"Every system + user prompt the assistant uses. Edit any field — blank fields fall back to the shipped default per-field. Brand voice is auto-prepended to system prompts, so don't repeat it here.", |
| 921 |
"yatra" |
| 922 |
) }) |
| 923 |
] }), |
| 924 |
/* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 925 |
Button, |
| 926 |
{ |
| 927 |
type: "button", |
| 928 |
variant: "outline", |
| 929 |
onClick: () => { |
| 930 |
if (window.confirm( |
| 931 |
__( |
| 932 |
"Reset every prompt to its shipped default? This wipes all your overrides.", |
| 933 |
"yatra" |
| 934 |
) |
| 935 |
)) { |
| 936 |
resetAllPromptsMutation.mutate(); |
| 937 |
} |
| 938 |
}, |
| 939 |
disabled: resetAllPromptsMutation.isPending, |
| 940 |
children: [ |
| 941 |
/* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "mr-2 h-4 w-4" }), |
| 942 |
__("Reset all", "yatra") |
| 943 |
] |
| 944 |
} |
| 945 |
) |
| 946 |
] }) }), |
| 947 |
/* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { children: promptsLoading ? /* @__PURE__ */ jsxRuntimeExports.jsx(ModuleSectionSkeleton, { lines: 5 }) : rows.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-gray-500", children: __("No prompts available.", "yatra") }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ |
| 948 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 rounded-md border border-blue-200 bg-blue-50/40 p-3 text-xs text-blue-800 dark:border-blue-500/40 dark:bg-blue-900/20 dark:text-blue-200", children: [ |
| 949 |
/* @__PURE__ */ jsxRuntimeExports.jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), |
| 950 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: __( |
| 951 |
"Use {{placeholders}} to inject runtime context (e.g. {{name}}, {{extra_context}}). Removing a placeholder means the assistant won't see that field — edit with care.", |
| 952 |
"yatra" |
| 953 |
) }) |
| 954 |
] }), |
| 955 |
groupOrder.map((cat) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 956 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400", children: cat }), |
| 957 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: grouped[cat].map((row) => { |
| 958 |
var _a2; |
| 959 |
const isOpen = expandedPrompt === row.task; |
| 960 |
const draft = promptDrafts[row.task] ?? { |
| 961 |
system: row.default.system, |
| 962 |
user: row.default.user, |
| 963 |
max_tokens: String(row.default.max_tokens), |
| 964 |
temperature: String(row.default.temperature) |
| 965 |
}; |
| 966 |
const saving = savePromptMutation.isPending && ((_a2 = savePromptMutation.variables) == null ? void 0 : _a2.task) === row.task; |
| 967 |
const resetting = resetPromptMutation.isPending && resetPromptMutation.variables === row.task; |
| 968 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 969 |
"div", |
| 970 |
{ |
| 971 |
className: "rounded-md border border-gray-200 dark:border-gray-700", |
| 972 |
children: [ |
| 973 |
/* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 974 |
"button", |
| 975 |
{ |
| 976 |
type: "button", |
| 977 |
onClick: () => setExpandedPrompt(isOpen ? null : row.task), |
| 978 |
className: "flex w-full items-start justify-between gap-2 px-3 py-2 text-left text-sm hover:bg-gray-50 dark:hover:bg-gray-800/60", |
| 979 |
children: [ |
| 980 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", children: [ |
| 981 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [ |
| 982 |
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium text-gray-900 dark:text-white", children: row.label }), |
| 983 |
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "rounded bg-gray-100 px-1.5 py-0.5 text-[10px] text-gray-600 dark:bg-gray-800 dark:text-gray-300", children: row.task }), |
| 984 |
row.has_override && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-blue-50 px-2 py-0.5 text-[10px] font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-300", children: __("Customized", "yatra") }) |
| 985 |
] }), |
| 986 |
row.description && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-0.5 text-[11px] leading-snug text-gray-500 dark:text-gray-400", children: row.description }) |
| 987 |
] }), |
| 988 |
isOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { className: "mt-1 h-4 w-4 shrink-0 text-gray-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "mt-1 h-4 w-4 shrink-0 text-gray-400" }) |
| 989 |
] |
| 990 |
} |
| 991 |
), |
| 992 |
isOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 border-t border-gray-200 p-3 dark:border-gray-700", children: [ |
| 993 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 994 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: `pr-sys-${row.task}`, children: __("System prompt", "yatra") }), |
| 995 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 996 |
"textarea", |
| 997 |
{ |
| 998 |
id: `pr-sys-${row.task}`, |
| 999 |
rows: 5, |
| 1000 |
className: "mt-1 w-full rounded-md border border-gray-300 bg-white p-2 font-mono text-xs dark:border-gray-600 dark:bg-gray-800", |
| 1001 |
value: draft.system, |
| 1002 |
onChange: (e) => setPromptDrafts((d) => ({ |
| 1003 |
...d, |
| 1004 |
[row.task]: { |
| 1005 |
...draft, |
| 1006 |
system: e.target.value |
| 1007 |
} |
| 1008 |
})) |
| 1009 |
} |
| 1010 |
) |
| 1011 |
] }), |
| 1012 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 1013 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: `pr-usr-${row.task}`, children: __("User prompt", "yatra") }), |
| 1014 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 1015 |
"textarea", |
| 1016 |
{ |
| 1017 |
id: `pr-usr-${row.task}`, |
| 1018 |
rows: 10, |
| 1019 |
className: "mt-1 w-full rounded-md border border-gray-300 bg-white p-2 font-mono text-xs dark:border-gray-600 dark:bg-gray-800", |
| 1020 |
value: draft.user, |
| 1021 |
onChange: (e) => setPromptDrafts((d) => ({ |
| 1022 |
...d, |
| 1023 |
[row.task]: { |
| 1024 |
...draft, |
| 1025 |
user: e.target.value |
| 1026 |
} |
| 1027 |
})) |
| 1028 |
} |
| 1029 |
) |
| 1030 |
] }), |
| 1031 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [ |
| 1032 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 1033 |
/* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: `pr-mt-${row.task}`, children: [ |
| 1034 |
__("Max tokens", "yatra"), |
| 1035 |
" ", |
| 1036 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-gray-400", children: [ |
| 1037 |
"(", |
| 1038 |
__("default", "yatra"), |
| 1039 |
":", |
| 1040 |
" ", |
| 1041 |
row.default.max_tokens, |
| 1042 |
")" |
| 1043 |
] }) |
| 1044 |
] }), |
| 1045 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 1046 |
Input, |
| 1047 |
{ |
| 1048 |
id: `pr-mt-${row.task}`, |
| 1049 |
type: "number", |
| 1050 |
min: 16, |
| 1051 |
max: 8e3, |
| 1052 |
value: draft.max_tokens, |
| 1053 |
onChange: (e) => setPromptDrafts((d) => ({ |
| 1054 |
...d, |
| 1055 |
[row.task]: { |
| 1056 |
...draft, |
| 1057 |
max_tokens: e.target.value |
| 1058 |
} |
| 1059 |
})) |
| 1060 |
} |
| 1061 |
) |
| 1062 |
] }), |
| 1063 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 1064 |
/* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { htmlFor: `pr-tp-${row.task}`, children: [ |
| 1065 |
__("Temperature", "yatra"), |
| 1066 |
" ", |
| 1067 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-gray-400", children: [ |
| 1068 |
"(", |
| 1069 |
__("default", "yatra"), |
| 1070 |
":", |
| 1071 |
" ", |
| 1072 |
row.default.temperature, |
| 1073 |
")" |
| 1074 |
] }) |
| 1075 |
] }), |
| 1076 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 1077 |
Input, |
| 1078 |
{ |
| 1079 |
id: `pr-tp-${row.task}`, |
| 1080 |
type: "number", |
| 1081 |
min: 0, |
| 1082 |
max: 2, |
| 1083 |
step: 0.1, |
| 1084 |
value: draft.temperature, |
| 1085 |
onChange: (e) => setPromptDrafts((d) => ({ |
| 1086 |
...d, |
| 1087 |
[row.task]: { |
| 1088 |
...draft, |
| 1089 |
temperature: e.target.value |
| 1090 |
} |
| 1091 |
})) |
| 1092 |
} |
| 1093 |
) |
| 1094 |
] }) |
| 1095 |
] }), |
| 1096 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "rounded-md border border-dashed border-gray-200 px-3 py-2 text-xs dark:border-gray-700", children: [ |
| 1097 |
/* @__PURE__ */ jsxRuntimeExports.jsx("summary", { className: "cursor-pointer text-gray-500 hover:text-gray-700 dark:text-gray-400", children: __("Show shipped defaults", "yatra") }), |
| 1098 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 space-y-2", children: [ |
| 1099 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 1100 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-gray-400", children: __("Default system", "yatra") }), |
| 1101 |
/* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "max-h-40 overflow-auto whitespace-pre-wrap rounded bg-gray-50 p-2 text-[11px] text-gray-700 dark:bg-gray-900 dark:text-gray-200", children: row.default.system }) |
| 1102 |
] }), |
| 1103 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 1104 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1 text-[10px] font-semibold uppercase tracking-wide text-gray-400", children: __("Default user", "yatra") }), |
| 1105 |
/* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "max-h-60 overflow-auto whitespace-pre-wrap rounded bg-gray-50 p-2 text-[11px] text-gray-700 dark:bg-gray-900 dark:text-gray-200", children: row.default.user }) |
| 1106 |
] }) |
| 1107 |
] }) |
| 1108 |
] }), |
| 1109 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [ |
| 1110 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 1111 |
Button, |
| 1112 |
{ |
| 1113 |
type: "button", |
| 1114 |
onClick: () => submitPrompt(row), |
| 1115 |
disabled: saving, |
| 1116 |
children: saving ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 1117 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), |
| 1118 |
__("Saving…", "yatra") |
| 1119 |
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 1120 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "mr-2 h-4 w-4" }), |
| 1121 |
__("Save", "yatra") |
| 1122 |
] }) |
| 1123 |
} |
| 1124 |
), |
| 1125 |
row.has_override && /* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 1126 |
Button, |
| 1127 |
{ |
| 1128 |
type: "button", |
| 1129 |
variant: "ghost", |
| 1130 |
onClick: () => resetPromptMutation.mutate(row.task), |
| 1131 |
disabled: resetting, |
| 1132 |
children: [ |
| 1133 |
resetting ? /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "mr-2 h-4 w-4" }), |
| 1134 |
__("Reset to default", "yatra") |
| 1135 |
] |
| 1136 |
} |
| 1137 |
) |
| 1138 |
] }) |
| 1139 |
] }) |
| 1140 |
] |
| 1141 |
}, |
| 1142 |
row.task |
| 1143 |
); |
| 1144 |
}) }) |
| 1145 |
] }, cat)) |
| 1146 |
] }) }) |
| 1147 |
] }); |
| 1148 |
}; |
| 1149 |
const currentSection = SECTIONS.find((s) => s.id === activeSection) ?? SECTIONS[0]; |
| 1150 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ |
| 1151 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 1152 |
PageHeader, |
| 1153 |
{ |
| 1154 |
description: __( |
| 1155 |
"Inline AI generation grounded in your trip data. Bring your own key.", |
| 1156 |
"yatra" |
| 1157 |
) |
| 1158 |
} |
| 1159 |
), |
| 1160 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-6 lg:grid-cols-[240px_1fr]", children: [ |
| 1161 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("aside", { className: "lg:sticky lg:top-4 lg:self-start", children: [ |
| 1162 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Card, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "p-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("nav", { className: "flex flex-col gap-1", children: SECTIONS.map((s) => { |
| 1163 |
const Icon = s.icon; |
| 1164 |
const active = s.id === activeSection; |
| 1165 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs( |
| 1166 |
"button", |
| 1167 |
{ |
| 1168 |
type: "button", |
| 1169 |
onClick: () => setActiveSection(s.id), |
| 1170 |
className: `flex items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors ${active ? "bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300" : "text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-gray-800"}`, |
| 1171 |
children: [ |
| 1172 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { className: "h-4 w-4 shrink-0" }), |
| 1173 |
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate", children: s.label }) |
| 1174 |
] |
| 1175 |
}, |
| 1176 |
s.id |
| 1177 |
); |
| 1178 |
}) }) }) }), |
| 1179 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 px-2 text-xs text-gray-500 dark:text-gray-400", children: currentSection.description }) |
| 1180 |
] }), |
| 1181 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ |
| 1182 |
activeSection === "keys" && renderKeys(), |
| 1183 |
activeSection === "voice" && renderVoice(), |
| 1184 |
activeSection === "prompts" && renderPrompts(), |
| 1185 |
activeSection === "usage" && renderUsage() |
| 1186 |
] }) |
| 1187 |
] }) |
| 1188 |
] }); |
| 1189 |
}; |
| 1190 |
const LimitField = ({ label, hint, field, draft, setDraft, schema }) => { |
| 1191 |
const range = schema[field]; |
| 1192 |
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ |
| 1193 |
/* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: `chat-limit-${field}`, children: label }), |
| 1194 |
/* @__PURE__ */ jsxRuntimeExports.jsx( |
| 1195 |
Input, |
| 1196 |
{ |
| 1197 |
id: `chat-limit-${field}`, |
| 1198 |
type: "number", |
| 1199 |
min: range == null ? void 0 : range.min, |
| 1200 |
max: range == null ? void 0 : range.max, |
| 1201 |
value: draft[field], |
| 1202 |
onChange: (e) => setDraft((prev) => ({ ...prev, [field]: e.target.value })) |
| 1203 |
} |
| 1204 |
), |
| 1205 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-0.5 text-[10px] text-gray-500 dark:text-gray-400", children: [ |
| 1206 |
hint, |
| 1207 |
range && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ |
| 1208 |
" ", |
| 1209 |
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-gray-400", children: [ |
| 1210 |
"(", |
| 1211 |
range.min, |
| 1212 |
"–", |
| 1213 |
range.max, |
| 1214 |
")" |
| 1215 |
] }) |
| 1216 |
] }) |
| 1217 |
] }) |
| 1218 |
] }); |
| 1219 |
}; |
| 1220 |
const Stat = ({ label, value }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-gray-200 p-3 dark:border-gray-700", children: [ |
| 1221 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] uppercase tracking-wide text-gray-500 dark:text-gray-400", children: label }), |
| 1222 |
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 text-lg font-semibold text-gray-900 dark:text-white", children: value }) |
| 1223 |
] }); |
| 1224 |
function extractError(e) { |
| 1225 |
var _a; |
| 1226 |
const data = ((_a = e == null ? void 0 : e.response) == null ? void 0 : _a.data) ?? (e == null ? void 0 : e.data) ?? null; |
| 1227 |
if (data && typeof data === "object" && typeof data.message === "string") { |
| 1228 |
return data.message; |
| 1229 |
} |
| 1230 |
return (e == null ? void 0 : e.message) || "AI request failed."; |
| 1231 |
} |
| 1232 |
export { |
| 1233 |
AiAssistant as default |
| 1234 |
}; |
| 1235 |
//# sourceMappingURL=AiAssistant-Bu_zHUnt.js.map |
| 1236 |
|