@parhelia/localization 0.1.12910 → 0.1.12912

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"TranslationsTitlebar.d.ts","sourceRoot":"","sources":["../../src/translation-center/TranslationsTitlebar.tsx"],"names":[],"mappings":"AAKA;;;GAGG;AACH,wBAAgB,oBAAoB,mDA8CnC"}
1
+ {"version":3,"file":"TranslationsTitlebar.d.ts","sourceRoot":"","sources":["../../src/translation-center/TranslationsTitlebar.tsx"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,oBAAoB,mDAqBnC"}
@@ -1,7 +1,6 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, SimpleIconButton, useEditContext } from "@parhelia/core";
3
- import { Globe, SquarePen } from "lucide-react";
4
- import { openLocalizeItemDialog } from "../LocalizeItemCommand";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { SimpleIconButton, useEditContext } from "@parhelia/core";
3
+ import { SquarePen } from "lucide-react";
5
4
  /**
6
5
  * TranslationsTitlebar - Titlebar content for the Translation Management workspace.
7
6
  * Shows desktop actions for the Translation Management workspace.
@@ -13,13 +12,5 @@ export function TranslationsTitlebar() {
13
12
  if (editContext.isMobile)
14
13
  return null;
15
14
  const { showAgentsWorkspaceEditor, setShowAgentsWorkspaceEditor } = editContext;
16
- const multiItemEnabled = editContext.configuration?.localization?.multiItem !== false;
17
- const currentItem = editContext.item;
18
- const canTranslate = multiItemEnabled || !!currentItem;
19
- return (_jsxs("div", { className: "flex w-full items-center justify-between gap-3 pr-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx("div", { "aria-hidden": "true", className: "border-border-default h-7 shrink-0 border-r" }), _jsxs(Button, { size: "sm", title: canTranslate ? "Translate" : "Open an item to translate", "data-testid": "translations-titlebar-translate-button", disabled: !canTranslate, onClick: () => {
20
- void openLocalizeItemDialog({
21
- editContext,
22
- items: currentItem ? [currentItem] : [],
23
- });
24
- }, children: [_jsx(Globe, { className: "h-4 w-4", strokeWidth: 1.5 }), "Translate"] })] }), _jsx(SimpleIconButton, { icon: _jsx(SquarePen, { className: "h-5 w-5", strokeWidth: 1 }), label: showAgentsWorkspaceEditor ? "Hide Editor" : "Show Editor", size: "large", "data-testid": "translations-editor-panel-toggle", selected: showAgentsWorkspaceEditor, onClick: () => setShowAgentsWorkspaceEditor(!showAgentsWorkspaceEditor) })] }));
15
+ return (_jsx("div", { className: "flex w-full items-center justify-end gap-3 pr-2", children: _jsx(SimpleIconButton, { icon: _jsx(SquarePen, { className: "h-5 w-5", strokeWidth: 1 }), label: showAgentsWorkspaceEditor ? "Hide Editor" : "Show Editor", size: "large", "data-testid": "translations-editor-panel-toggle", selected: showAgentsWorkspaceEditor, onClick: () => setShowAgentsWorkspaceEditor(!showAgentsWorkspaceEditor) }) }));
25
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parhelia/localization",
3
- "version": "0.1.12910",
3
+ "version": "0.1.12912",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,3 +0,0 @@
1
- import { TranslationStepProps } from "./types";
2
- export declare function PromptCustomizationStep({ isActive, data, setData, onStepCompleted, editContext, }: TranslationStepProps): import("react/jsx-runtime").JSX.Element;
3
- //# sourceMappingURL=PromptCustomizationStep.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PromptCustomizationStep.d.ts","sourceRoot":"","sources":["../../src/steps/PromptCustomizationStep.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAyB,MAAM,SAAS,CAAC;AAItE,wBAAgB,uBAAuB,CAAC,EACtC,QAAe,EACf,IAAI,EACJ,OAAO,EACP,eAAe,EACf,WAAW,GACZ,EAAE,oBAAoB,2CAsctB"}
@@ -1,257 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { Input, Select, Spinner, Textarea } from "@parhelia/core";
4
- import { WizardStepShell } from "./WizardStepShell";
5
- import { suggestBatchName } from "../api/discovery";
6
- export function PromptCustomizationStep({ isActive = true, data, setData, onStepCompleted, editContext, }) {
7
- const [customPrompt, setCustomPrompt] = useState("");
8
- const [customizationType, setCustomizationType] = useState("extend");
9
- // Local mirror of the wizard's batch name so typing is responsive.
10
- const [batchName, setBatchName] = useState(data.batchName ?? "");
11
- const [isSuggesting, setIsSuggesting] = useState(false);
12
- // Whether the user has manually edited the name. If so, we never overwrite
13
- // it with a fresh AI suggestion.
14
- const userTouchedNameRef = useRef(!!data.batchName);
15
- const dataRef = useRef(data);
16
- useEffect(() => {
17
- dataRef.current = data;
18
- }, [data]);
19
- // Push name changes back into wizard data (debounced via simple equality).
20
- useEffect(() => {
21
- if ((dataRef.current.batchName ?? "") === batchName)
22
- return;
23
- setData({ ...dataRef.current, batchName });
24
- }, [batchName, setData]);
25
- // Auto-suggest a name when the step becomes active and the user hasn't
26
- // already provided/edited one. Fires once per item+language combination so
27
- // navigating away and back doesn't re-fetch.
28
- const lastSuggestionKeyRef = useRef("");
29
- const sessionId = editContext?.sessionId;
30
- useEffect(() => {
31
- if (!isActive)
32
- return;
33
- if (userTouchedNameRef.current)
34
- return;
35
- const itemIds = (data.selectionTreeItems && data.selectionTreeItems.length > 0
36
- ? data.selectionTreeItems.map((s) => s.descriptor.id)
37
- : data.items.map((i) => i.descriptor.id)).filter(Boolean);
38
- const langs = [...data.targetLanguages].sort();
39
- if (itemIds.length === 0 || langs.length === 0)
40
- return;
41
- const key = JSON.stringify({ itemIds: [...itemIds].sort(), langs });
42
- if (lastSuggestionKeyRef.current === key)
43
- return;
44
- lastSuggestionKeyRef.current = key;
45
- let cancelled = false;
46
- setIsSuggesting(true);
47
- void (async () => {
48
- try {
49
- const includeSubitems = !!data.selectionTreeItems?.some((s) => s.includeSubitems);
50
- const name = await suggestBatchName({ itemIds, targetLanguages: langs, includeSubitems }, sessionId);
51
- if (cancelled || userTouchedNameRef.current)
52
- return;
53
- if (name)
54
- setBatchName(name);
55
- }
56
- finally {
57
- if (!cancelled)
58
- setIsSuggesting(false);
59
- }
60
- })();
61
- return () => {
62
- cancelled = true;
63
- };
64
- }, [
65
- isActive,
66
- sessionId,
67
- data.selectionTreeItems,
68
- data.items,
69
- data.targetLanguages,
70
- ]);
71
- const customPromptRef = useRef(customPrompt);
72
- const customizationTypeRef = useRef(customizationType);
73
- const hasInitializedRef = useRef(false);
74
- const lastProviderRef = useRef(data.translationProvider);
75
- const updateTimerRef = useRef(null);
76
- useEffect(() => {
77
- customPromptRef.current = customPrompt;
78
- }, [customPrompt]);
79
- useEffect(() => {
80
- customizationTypeRef.current = customizationType;
81
- }, [customizationType]);
82
- const selectedProvider = useMemo(() => data.translationProviders.find((p) => p.name === data.translationProvider), [data.translationProviders, data.translationProvider]);
83
- const serviceData = useMemo(() => {
84
- if (!data.serviceCustomData || !data.translationProvider)
85
- return null;
86
- return data.serviceCustomData.get(data.translationProvider);
87
- }, [data.serviceCustomData, data.translationProvider]);
88
- const enableCustomPrompt = serviceData?.enableCustomPrompt === true;
89
- const supportsPromptCustomization = data.translationProvider === "AI";
90
- const defaultPrompt = useMemo(() => {
91
- const prompt = selectedProvider?.defaultPrompt;
92
- return prompt && prompt.trim() ? prompt : null;
93
- }, [selectedProvider?.defaultPrompt]);
94
- const hasDefaultPrompt = defaultPrompt != null && defaultPrompt.length > 0;
95
- useEffect(() => {
96
- if (lastProviderRef.current !== data.translationProvider) {
97
- hasInitializedRef.current = false;
98
- lastProviderRef.current = data.translationProvider;
99
- }
100
- if (hasInitializedRef.current)
101
- return;
102
- const nextCustomizationType = serviceData?.promptCustomizationType ||
103
- "extend";
104
- let nextCustomPrompt = serviceData?.customPrompt || "";
105
- if (hasDefaultPrompt &&
106
- nextCustomizationType === "extend" &&
107
- nextCustomPrompt.startsWith(defaultPrompt || "")) {
108
- nextCustomPrompt = nextCustomPrompt.slice((defaultPrompt || "").length);
109
- nextCustomPrompt = nextCustomPrompt.replace(/^\s*\n\s*\n?/, "");
110
- }
111
- if (enableCustomPrompt && serviceData) {
112
- setCustomPrompt(nextCustomPrompt);
113
- setCustomizationType(nextCustomizationType);
114
- }
115
- else {
116
- setCustomPrompt("");
117
- setCustomizationType("extend");
118
- }
119
- hasInitializedRef.current = true;
120
- }, [
121
- data.translationProvider,
122
- defaultPrompt,
123
- enableCustomPrompt,
124
- hasDefaultPrompt,
125
- serviceData,
126
- ]);
127
- useEffect(() => {
128
- if (!isActive)
129
- return;
130
- onStepCompleted(!!data.translationProvider);
131
- }, [data.translationProvider, isActive, onStepCompleted]);
132
- const handleProviderChange = (e) => {
133
- const newProvider = e.target.value;
134
- const newServiceCustomData = new Map();
135
- data.serviceCustomData?.forEach((value, key) => {
136
- if (key !== "AI" || newProvider === "AI") {
137
- newServiceCustomData.set(key, value);
138
- }
139
- });
140
- const newData = {
141
- ...data,
142
- translationProvider: newProvider,
143
- serviceCustomData: newServiceCustomData,
144
- };
145
- setData(newData);
146
- };
147
- const handleCustomPromptToggle = (enabled) => {
148
- const newServiceCustomData = new Map(data.serviceCustomData || new Map());
149
- if (enabled) {
150
- newServiceCustomData.set(data.translationProvider, {
151
- enableCustomPrompt: true,
152
- customPrompt: "",
153
- promptCustomizationType: "extend",
154
- });
155
- }
156
- else {
157
- newServiceCustomData.delete(data.translationProvider);
158
- }
159
- setData({
160
- ...data,
161
- serviceCustomData: newServiceCustomData,
162
- });
163
- };
164
- const previewPrompt = useMemo(() => {
165
- if (!enableCustomPrompt || !customPrompt.trim()) {
166
- return defaultPrompt || "";
167
- }
168
- if (!hasDefaultPrompt) {
169
- return customPrompt;
170
- }
171
- if (customizationType === "replace") {
172
- return customPrompt;
173
- }
174
- return `${defaultPrompt}\n\n${customPrompt}`;
175
- }, [
176
- customPrompt,
177
- customizationType,
178
- defaultPrompt,
179
- enableCustomPrompt,
180
- hasDefaultPrompt,
181
- ]);
182
- const updateParentData = useCallback(() => {
183
- if (!data.translationProvider)
184
- return;
185
- const trimmedCustomPrompt = customPromptRef.current.trim();
186
- const currentCustomizationType = customizationTypeRef.current;
187
- const newServiceCustomData = new Map(data.serviceCustomData || new Map());
188
- const currentServiceData = data.serviceCustomData?.get(data.translationProvider);
189
- if (enableCustomPrompt) {
190
- const nextServiceData = {
191
- enableCustomPrompt: true,
192
- customPrompt: trimmedCustomPrompt,
193
- promptCustomizationType: currentCustomizationType,
194
- };
195
- const isSame = currentServiceData?.enableCustomPrompt === true &&
196
- (currentServiceData.customPrompt || "") === trimmedCustomPrompt &&
197
- (currentServiceData.promptCustomizationType || "extend") ===
198
- currentCustomizationType;
199
- if (isSame)
200
- return;
201
- newServiceCustomData.set(data.translationProvider, nextServiceData);
202
- }
203
- else {
204
- if (!currentServiceData)
205
- return;
206
- newServiceCustomData.delete(data.translationProvider);
207
- }
208
- setData({
209
- ...data,
210
- serviceCustomData: newServiceCustomData,
211
- });
212
- }, [data, enableCustomPrompt, setData]);
213
- useEffect(() => {
214
- if (!isActive || !supportsPromptCustomization)
215
- return;
216
- if (updateTimerRef.current) {
217
- clearTimeout(updateTimerRef.current);
218
- }
219
- updateTimerRef.current = setTimeout(() => {
220
- updateParentData();
221
- }, 100);
222
- return () => {
223
- if (updateTimerRef.current) {
224
- clearTimeout(updateTimerRef.current);
225
- }
226
- };
227
- }, [
228
- customPrompt,
229
- customizationType,
230
- isActive,
231
- supportsPromptCustomization,
232
- updateParentData,
233
- ]);
234
- return (_jsx(WizardStepShell, { fillHeight: true, testId: "prompt-customization-step", children: _jsxs("div", { className: "mx-auto flex min-h-0 w-full max-w-3xl flex-1 flex-col gap-6 overflow-y-auto", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("label", { htmlFor: "translation-batch-name", className: "text-[11px] font-bold tracking-wider text-neutral-grey-50 ", children: "Name" }), isSuggesting && (_jsx("div", { role: "status", "aria-label": "Suggesting name", className: "text-muted-foreground flex h-3.5 w-3.5 items-center justify-center", children: _jsx(Spinner, { size: "xs" }) }))] }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: "A short label for this translation batch." }), _jsx(Input, { id: "translation-batch-name", type: "text", value: batchName, placeholder: "Translation batch", onChange: (e) => {
235
- userTouchedNameRef.current = true;
236
- setBatchName(e.target.value);
237
- }, className: "mt-2", "data-testid": "translation-batch-name-input" })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[11px] font-bold tracking-wider text-neutral-grey-50 ", children: "Translation provider" }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: "\"Create Versions\" will create new language versions without automatic translation." }), _jsx(Select, { value: data.translationProvider || "", onValueChange: (value) => handleProviderChange({
238
- target: { value },
239
- }), options: data.translationProviders.map((provider) => ({
240
- value: provider.name,
241
- label: provider.displayName || provider.name,
242
- })), placeholder: "Select a provider\u2026", size: "sm", className: "mt-2 w-full", "data-testid": "translation-provider-select" })] }), supportsPromptCustomization && (_jsxs(_Fragment, { children: [_jsxs("label", { className: "-mb-2 flex cursor-pointer items-start gap-2.5", children: [_jsx("input", { type: "checkbox", checked: enableCustomPrompt, onChange: (e) => handleCustomPromptToggle(e.target.checked), className: "mt-0.5 h-3.5 w-3.5 rounded border-border-default text-[var(--color-highlight-100)] accent-[var(--color-highlight-100)] focus:ring-[var(--color-highlight-100)]", "data-testid": "enable-custom-prompt-checkbox" }), _jsxs("div", { className: "min-w-0", children: [_jsx("span", { className: "block text-[13px] font-medium text-neutral-grey-100", children: "Customize translation prompt" }), _jsx("span", { className: "text-muted-foreground mt-0.5 block text-xs", children: "Override or extend the provider's default instructions." })] })] }), enableCustomPrompt && (_jsxs(_Fragment, { children: [hasDefaultPrompt && (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("span", { className: "text-[11px] font-bold tracking-wider text-neutral-grey-50 ", children: "Default prompt" }), _jsx("pre", { className: "mt-2 max-h-40 overflow-y-auto rounded-md bg-neutral-grey-5/70 p-3 font-mono text-xs leading-relaxed whitespace-pre-wrap text-neutral-grey-100", children: defaultPrompt })] }), _jsxs("div", { children: [_jsx("span", { className: "text-[11px] font-bold tracking-wider text-neutral-grey-50 ", children: "Customization type" }), _jsx("div", { className: "mt-2 grid gap-2 sm:grid-cols-2", children: [
243
- {
244
- value: "extend",
245
- title: "Extend",
246
- hint: "Append to the default prompt.",
247
- },
248
- {
249
- value: "replace",
250
- title: "Replace",
251
- hint: "Use the custom prompt only.",
252
- },
253
- ].map((opt) => {
254
- const selected = customizationType === opt.value;
255
- return (_jsxs("label", { className: `group flex cursor-pointer items-start gap-2.5 rounded-lg p-2.5 transition-colors ${selected ? "bg-primary/5" : "hover:bg-neutral-grey-5"}`, children: [_jsx("input", { type: "radio", name: "customizationType", value: opt.value, checked: selected, onChange: () => setCustomizationType(opt.value), className: "mt-0.5 h-3.5 w-3.5 border-border-default text-[var(--color-highlight-100)] accent-[var(--color-highlight-100)] focus:ring-[var(--color-highlight-100)]", "data-testid": `customization-type-${opt.value}` }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs font-medium text-neutral-grey-100", children: opt.title }), _jsx("div", { className: "text-muted-foreground mt-0.5 text-[11px]", children: opt.hint })] })] }, opt.value));
256
- }) })] })] })), _jsxs("div", { children: [_jsx("span", { className: "text-[11px] font-bold tracking-wider text-neutral-grey-50 ", children: "Custom prompt" }), _jsx(Textarea, { value: customPrompt, onChange: (e) => setCustomPrompt(e.target.value), className: "mt-2 font-mono", rows: 6, placeholder: "Enter your custom prompt instructions here\u2026", "data-testid": "custom-prompt-textarea" })] }), hasDefaultPrompt && (_jsxs("div", { children: [_jsx("span", { className: "text-[11px] font-bold tracking-wider text-neutral-grey-50 ", children: "Preview" }), _jsx("pre", { className: "mt-2 max-h-52 overflow-y-auto rounded-md bg-neutral-grey-5/70 p-3 font-mono text-xs leading-relaxed whitespace-pre-wrap text-neutral-grey-100", children: previewPrompt })] }))] }))] }))] }) }));
257
- }