@consilioweb/payload-support 0.6.4 → 0.7.0
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.
- package/README.md +1 -1
- package/dist/components/TicketConversation/hooks/useAI.cjs +2 -2
- package/dist/components/TicketConversation/hooks/useAI.js +2 -2
- package/dist/components/TicketConversation/index.cjs +97 -4
- package/dist/components/TicketConversation/index.js +97 -4
- package/dist/index.cjs +55 -11
- package/dist/index.js +55 -11
- package/package.json +1 -1
- package/src/collections/Tickets.ts +44 -9
- package/src/components/TicketConversation/hooks/useAI.ts +2 -2
- package/src/components/TicketConversation/index.tsx +96 -6
- package/src/endpoints/ai.ts +12 -2
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
>
|
|
38
38
|
> And ensure all `@consilioweb/*` packages are in `transpilePackages` in your `next.config.ts`:
|
|
39
39
|
> ```ts
|
|
40
|
-
> transpilePackages: ['@consilioweb/seo-analyzer', '@consilioweb/admin-nav', /* ...other @consilioweb packages */],
|
|
40
|
+
> transpilePackages: ['@consilioweb/payload-seo-analyzer', '@consilioweb/payload-admin-nav', /* ...other @consilioweb packages */],
|
|
41
41
|
> ```
|
|
42
42
|
>
|
|
43
43
|
> ✅ **Next.js 15** works without any workaround.
|
|
@@ -75,7 +75,7 @@ function useAI(messages, client, ticketSubject, replyBody, setReplyBody, setRepl
|
|
|
75
75
|
}
|
|
76
76
|
setAiReplying(false);
|
|
77
77
|
};
|
|
78
|
-
const handleAiRewrite = async () => {
|
|
78
|
+
const handleAiRewrite = async (style = "auto") => {
|
|
79
79
|
if (!replyBody.trim()) return;
|
|
80
80
|
setAiRewriting(true);
|
|
81
81
|
try {
|
|
@@ -83,7 +83,7 @@ function useAI(messages, client, ticketSubject, replyBody, setReplyBody, setRepl
|
|
|
83
83
|
method: "POST",
|
|
84
84
|
headers: { "Content-Type": "application/json" },
|
|
85
85
|
credentials: "include",
|
|
86
|
-
body: JSON.stringify({ action: "rewrite", text: replyBody })
|
|
86
|
+
body: JSON.stringify({ action: "rewrite", text: replyBody, style })
|
|
87
87
|
});
|
|
88
88
|
if (res.ok) {
|
|
89
89
|
const data = await res.json();
|
|
@@ -74,7 +74,7 @@ function useAI(messages, client, ticketSubject, replyBody, setReplyBody, setRepl
|
|
|
74
74
|
}
|
|
75
75
|
setAiReplying(false);
|
|
76
76
|
};
|
|
77
|
-
const handleAiRewrite = async () => {
|
|
77
|
+
const handleAiRewrite = async (style = "auto") => {
|
|
78
78
|
if (!replyBody.trim()) return;
|
|
79
79
|
setAiRewriting(true);
|
|
80
80
|
try {
|
|
@@ -82,7 +82,7 @@ function useAI(messages, client, ticketSubject, replyBody, setReplyBody, setRepl
|
|
|
82
82
|
method: "POST",
|
|
83
83
|
headers: { "Content-Type": "application/json" },
|
|
84
84
|
credentials: "include",
|
|
85
|
-
body: JSON.stringify({ action: "rewrite", text: replyBody })
|
|
85
|
+
body: JSON.stringify({ action: "rewrite", text: replyBody, style })
|
|
86
86
|
});
|
|
87
87
|
if (res.ok) {
|
|
88
88
|
const data = await res.json();
|
|
@@ -72,6 +72,100 @@ const layoutStyles = {
|
|
|
72
72
|
top: "80px"
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
|
+
const REWRITE_STYLES = [
|
|
76
|
+
{ id: "auto", label: "\u270F\uFE0F Auto", desc: "Garde le ton actuel" },
|
|
77
|
+
{ id: "tutoyer", label: "\u{1F44B} Tutoyer", desc: "Passe en tu" },
|
|
78
|
+
{ id: "vouvoyer", label: "\u{1F3A9} Vouvoyer", desc: "Passe en vous" },
|
|
79
|
+
{ id: "formel", label: "\u{1F4BC} Formel", desc: "Ton professionnel" },
|
|
80
|
+
{ id: "amical", label: "\u{1F60A} Amical", desc: "Ton chaleureux" },
|
|
81
|
+
{ id: "court", label: "\u26A1 Court", desc: "Version concise" }
|
|
82
|
+
];
|
|
83
|
+
const RewriteDropdown = ({ disabled, loading, onSelect }) => {
|
|
84
|
+
const [open, setOpen] = React.useState(false);
|
|
85
|
+
const ref = React__default.default.useRef(null);
|
|
86
|
+
React__default.default.useEffect(() => {
|
|
87
|
+
if (!open) return;
|
|
88
|
+
const close = (e) => {
|
|
89
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
90
|
+
};
|
|
91
|
+
document.addEventListener("mousedown", close);
|
|
92
|
+
return () => document.removeEventListener("mousedown", close);
|
|
93
|
+
}, [open]);
|
|
94
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, style: { position: "relative", display: "inline-block" }, children: [
|
|
95
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
96
|
+
"button",
|
|
97
|
+
{
|
|
98
|
+
type: "button",
|
|
99
|
+
onClick: () => setOpen(!open),
|
|
100
|
+
disabled,
|
|
101
|
+
style: {
|
|
102
|
+
...constants.s.outlineBtn("#0891b2", disabled),
|
|
103
|
+
fontSize: "11px",
|
|
104
|
+
padding: "3px 10px",
|
|
105
|
+
borderRadius: "14px",
|
|
106
|
+
display: "flex",
|
|
107
|
+
alignItems: "center",
|
|
108
|
+
gap: "4px"
|
|
109
|
+
},
|
|
110
|
+
children: [
|
|
111
|
+
loading ? "Reformulation..." : "\u270F\uFE0F Reformuler",
|
|
112
|
+
!loading && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 9, opacity: 0.7 }, children: "\u25BC" })
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
open && !disabled && !loading && /* @__PURE__ */ jsxRuntime.jsx(
|
|
117
|
+
"div",
|
|
118
|
+
{
|
|
119
|
+
style: {
|
|
120
|
+
position: "absolute",
|
|
121
|
+
bottom: "100%",
|
|
122
|
+
left: 0,
|
|
123
|
+
marginBottom: 4,
|
|
124
|
+
background: "var(--theme-elevation-0, #fff)",
|
|
125
|
+
border: "1px solid var(--theme-elevation-200, #e5e7eb)",
|
|
126
|
+
borderRadius: 8,
|
|
127
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
|
|
128
|
+
zIndex: 100,
|
|
129
|
+
minWidth: 180,
|
|
130
|
+
overflow: "hidden"
|
|
131
|
+
},
|
|
132
|
+
children: REWRITE_STYLES.map((style) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
133
|
+
"button",
|
|
134
|
+
{
|
|
135
|
+
type: "button",
|
|
136
|
+
onClick: () => {
|
|
137
|
+
setOpen(false);
|
|
138
|
+
onSelect(style.id);
|
|
139
|
+
},
|
|
140
|
+
style: {
|
|
141
|
+
display: "flex",
|
|
142
|
+
flexDirection: "column",
|
|
143
|
+
width: "100%",
|
|
144
|
+
padding: "8px 12px",
|
|
145
|
+
border: "none",
|
|
146
|
+
background: "transparent",
|
|
147
|
+
cursor: "pointer",
|
|
148
|
+
textAlign: "left",
|
|
149
|
+
borderBottom: "1px solid var(--theme-elevation-100, #f3f4f6)",
|
|
150
|
+
transition: "background 120ms"
|
|
151
|
+
},
|
|
152
|
+
onMouseEnter: (e) => {
|
|
153
|
+
e.target.style.background = "var(--theme-elevation-50, #f9fafb)";
|
|
154
|
+
},
|
|
155
|
+
onMouseLeave: (e) => {
|
|
156
|
+
e.target.style.background = "transparent";
|
|
157
|
+
},
|
|
158
|
+
children: [
|
|
159
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, fontWeight: 600, color: "var(--theme-text, #111)" }, children: style.label }),
|
|
160
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, color: "var(--theme-elevation-500, #6b7280)" }, children: style.desc })
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
style.id
|
|
164
|
+
))
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
] });
|
|
168
|
+
};
|
|
75
169
|
const TicketConversation = () => {
|
|
76
170
|
const { id } = useDocumentIdFromUrl();
|
|
77
171
|
const [features] = React.useState(() => config.getFeatures());
|
|
@@ -805,12 +899,11 @@ const TicketConversation = () => {
|
|
|
805
899
|
}
|
|
806
900
|
),
|
|
807
901
|
features.ai && /* @__PURE__ */ jsxRuntime.jsx(
|
|
808
|
-
|
|
902
|
+
RewriteDropdown,
|
|
809
903
|
{
|
|
810
|
-
onClick: handleAiRewrite,
|
|
811
904
|
disabled: aiRewriting || !replyBody.trim(),
|
|
812
|
-
|
|
813
|
-
|
|
905
|
+
loading: aiRewriting,
|
|
906
|
+
onSelect: (style) => handleAiRewrite(style)
|
|
814
907
|
}
|
|
815
908
|
),
|
|
816
909
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -67,6 +67,100 @@ const layoutStyles = {
|
|
|
67
67
|
top: "80px"
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
|
+
const REWRITE_STYLES = [
|
|
71
|
+
{ id: "auto", label: "\u270F\uFE0F Auto", desc: "Garde le ton actuel" },
|
|
72
|
+
{ id: "tutoyer", label: "\u{1F44B} Tutoyer", desc: "Passe en tu" },
|
|
73
|
+
{ id: "vouvoyer", label: "\u{1F3A9} Vouvoyer", desc: "Passe en vous" },
|
|
74
|
+
{ id: "formel", label: "\u{1F4BC} Formel", desc: "Ton professionnel" },
|
|
75
|
+
{ id: "amical", label: "\u{1F60A} Amical", desc: "Ton chaleureux" },
|
|
76
|
+
{ id: "court", label: "\u26A1 Court", desc: "Version concise" }
|
|
77
|
+
];
|
|
78
|
+
const RewriteDropdown = ({ disabled, loading, onSelect }) => {
|
|
79
|
+
const [open, setOpen] = useState(false);
|
|
80
|
+
const ref = React.useRef(null);
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
if (!open) return;
|
|
83
|
+
const close = (e) => {
|
|
84
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
85
|
+
};
|
|
86
|
+
document.addEventListener("mousedown", close);
|
|
87
|
+
return () => document.removeEventListener("mousedown", close);
|
|
88
|
+
}, [open]);
|
|
89
|
+
return /* @__PURE__ */ jsxs("div", { ref, style: { position: "relative", display: "inline-block" }, children: [
|
|
90
|
+
/* @__PURE__ */ jsxs(
|
|
91
|
+
"button",
|
|
92
|
+
{
|
|
93
|
+
type: "button",
|
|
94
|
+
onClick: () => setOpen(!open),
|
|
95
|
+
disabled,
|
|
96
|
+
style: {
|
|
97
|
+
...s.outlineBtn("#0891b2", disabled),
|
|
98
|
+
fontSize: "11px",
|
|
99
|
+
padding: "3px 10px",
|
|
100
|
+
borderRadius: "14px",
|
|
101
|
+
display: "flex",
|
|
102
|
+
alignItems: "center",
|
|
103
|
+
gap: "4px"
|
|
104
|
+
},
|
|
105
|
+
children: [
|
|
106
|
+
loading ? "Reformulation..." : "\u270F\uFE0F Reformuler",
|
|
107
|
+
!loading && /* @__PURE__ */ jsx("span", { style: { fontSize: 9, opacity: 0.7 }, children: "\u25BC" })
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
open && !disabled && !loading && /* @__PURE__ */ jsx(
|
|
112
|
+
"div",
|
|
113
|
+
{
|
|
114
|
+
style: {
|
|
115
|
+
position: "absolute",
|
|
116
|
+
bottom: "100%",
|
|
117
|
+
left: 0,
|
|
118
|
+
marginBottom: 4,
|
|
119
|
+
background: "var(--theme-elevation-0, #fff)",
|
|
120
|
+
border: "1px solid var(--theme-elevation-200, #e5e7eb)",
|
|
121
|
+
borderRadius: 8,
|
|
122
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
|
|
123
|
+
zIndex: 100,
|
|
124
|
+
minWidth: 180,
|
|
125
|
+
overflow: "hidden"
|
|
126
|
+
},
|
|
127
|
+
children: REWRITE_STYLES.map((style) => /* @__PURE__ */ jsxs(
|
|
128
|
+
"button",
|
|
129
|
+
{
|
|
130
|
+
type: "button",
|
|
131
|
+
onClick: () => {
|
|
132
|
+
setOpen(false);
|
|
133
|
+
onSelect(style.id);
|
|
134
|
+
},
|
|
135
|
+
style: {
|
|
136
|
+
display: "flex",
|
|
137
|
+
flexDirection: "column",
|
|
138
|
+
width: "100%",
|
|
139
|
+
padding: "8px 12px",
|
|
140
|
+
border: "none",
|
|
141
|
+
background: "transparent",
|
|
142
|
+
cursor: "pointer",
|
|
143
|
+
textAlign: "left",
|
|
144
|
+
borderBottom: "1px solid var(--theme-elevation-100, #f3f4f6)",
|
|
145
|
+
transition: "background 120ms"
|
|
146
|
+
},
|
|
147
|
+
onMouseEnter: (e) => {
|
|
148
|
+
e.target.style.background = "var(--theme-elevation-50, #f9fafb)";
|
|
149
|
+
},
|
|
150
|
+
onMouseLeave: (e) => {
|
|
151
|
+
e.target.style.background = "transparent";
|
|
152
|
+
},
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 12, fontWeight: 600, color: "var(--theme-text, #111)" }, children: style.label }),
|
|
155
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 10, color: "var(--theme-elevation-500, #6b7280)" }, children: style.desc })
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
style.id
|
|
159
|
+
))
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
] });
|
|
163
|
+
};
|
|
70
164
|
const TicketConversation = () => {
|
|
71
165
|
const { id } = useDocumentIdFromUrl();
|
|
72
166
|
const [features] = useState(() => getFeatures());
|
|
@@ -800,12 +894,11 @@ const TicketConversation = () => {
|
|
|
800
894
|
}
|
|
801
895
|
),
|
|
802
896
|
features.ai && /* @__PURE__ */ jsx(
|
|
803
|
-
|
|
897
|
+
RewriteDropdown,
|
|
804
898
|
{
|
|
805
|
-
onClick: handleAiRewrite,
|
|
806
899
|
disabled: aiRewriting || !replyBody.trim(),
|
|
807
|
-
|
|
808
|
-
|
|
900
|
+
loading: aiRewriting,
|
|
901
|
+
onSelect: (style) => handleAiRewrite(style)
|
|
809
902
|
}
|
|
810
903
|
),
|
|
811
904
|
/* @__PURE__ */ jsx(
|
package/dist/index.cjs
CHANGED
|
@@ -280,9 +280,18 @@ R\xE9dige une r\xE9ponse appropri\xE9e au dernier message du client. Sois concis
|
|
|
280
280
|
if (!aiSettings.enableRewrite) {
|
|
281
281
|
return Response.json({ rewritten: "", disabled: true });
|
|
282
282
|
}
|
|
283
|
-
const { text } = body;
|
|
283
|
+
const { text, style } = body;
|
|
284
284
|
if (!text?.trim()) return Response.json({ error: "text required" }, { status: 400 });
|
|
285
|
-
const
|
|
285
|
+
const styleInstructions = {
|
|
286
|
+
auto: "Garde le m\xEAme ton (tutoiement/vouvoiement).",
|
|
287
|
+
tutoyer: "Utilise le tutoiement. Si le texte vouvoie, convertis en tutoiement.",
|
|
288
|
+
vouvoyer: "Utilise le vouvoiement. Si le texte tutoie, convertis en vouvoiement.",
|
|
289
|
+
formel: "Adopte un ton formel et professionnel avec vouvoiement.",
|
|
290
|
+
court: "Raccourcis le texte au maximum tout en gardant le sens. Sois concis et direct.",
|
|
291
|
+
amical: "Adopte un ton chaleureux et amical avec tutoiement."
|
|
292
|
+
};
|
|
293
|
+
const styleGuide = styleInstructions[style || "auto"] || styleInstructions.auto;
|
|
294
|
+
const prompt = `Tu es un agent de support technique professionnel. Reformule le texte ci-dessous de mani\xE8re plus professionnelle et corrige les fautes d'orthographe/grammaire. ${styleGuide} Ne change pas le fond du message, am\xE9liore uniquement la forme. R\xE9ponds UNIQUEMENT avec le texte reformul\xE9, sans commentaire ni explication.
|
|
286
295
|
|
|
287
296
|
Texte original :
|
|
288
297
|
${text}`;
|
|
@@ -5672,6 +5681,37 @@ function createTicketsCollection(slugs, options) {
|
|
|
5672
5681
|
});
|
|
5673
5682
|
}
|
|
5674
5683
|
const billingFields = [
|
|
5684
|
+
{
|
|
5685
|
+
type: "row",
|
|
5686
|
+
fields: [
|
|
5687
|
+
{
|
|
5688
|
+
name: "billingType",
|
|
5689
|
+
type: "select",
|
|
5690
|
+
label: "Type de facturation",
|
|
5691
|
+
defaultValue: "hourly",
|
|
5692
|
+
options: [
|
|
5693
|
+
{ label: "Au temps pass\xE9", value: "hourly" },
|
|
5694
|
+
{ label: "Forfait", value: "flat" }
|
|
5695
|
+
],
|
|
5696
|
+
admin: { width: "33%" }
|
|
5697
|
+
},
|
|
5698
|
+
{
|
|
5699
|
+
name: "flatRateAmount",
|
|
5700
|
+
type: "number",
|
|
5701
|
+
label: "Montant forfait (EUR)",
|
|
5702
|
+
admin: {
|
|
5703
|
+
width: "33%",
|
|
5704
|
+
condition: (data) => data?.billingType === "flat"
|
|
5705
|
+
}
|
|
5706
|
+
},
|
|
5707
|
+
{
|
|
5708
|
+
name: "billedAmount",
|
|
5709
|
+
type: "number",
|
|
5710
|
+
label: "Montant factur\xE9 (EUR)",
|
|
5711
|
+
admin: { width: "33%" }
|
|
5712
|
+
}
|
|
5713
|
+
]
|
|
5714
|
+
},
|
|
5675
5715
|
{
|
|
5676
5716
|
type: "row",
|
|
5677
5717
|
fields: [
|
|
@@ -5712,14 +5752,8 @@ function createTicketsCollection(slugs, options) {
|
|
|
5712
5752
|
{
|
|
5713
5753
|
name: "paidAt",
|
|
5714
5754
|
type: "date",
|
|
5715
|
-
label: "
|
|
5755
|
+
label: "Pay\xE9 le",
|
|
5716
5756
|
admin: { width: "33%", date: { displayFormat: "dd/MM/yyyy HH:mm" } }
|
|
5717
|
-
},
|
|
5718
|
-
{
|
|
5719
|
-
name: "billedAmount",
|
|
5720
|
-
type: "number",
|
|
5721
|
-
label: "Montant facture (EUR)",
|
|
5722
|
-
admin: { width: "33%" }
|
|
5723
5757
|
}
|
|
5724
5758
|
]
|
|
5725
5759
|
}
|
|
@@ -5829,7 +5863,7 @@ function createTicketsCollection(slugs, options) {
|
|
|
5829
5863
|
]
|
|
5830
5864
|
},
|
|
5831
5865
|
// Sidebar
|
|
5832
|
-
{ name: "ticketNumber", type: "text", unique: true, label: "N\xB0 Ticket", admin: {
|
|
5866
|
+
{ name: "ticketNumber", type: "text", unique: true, label: "N\xB0 Ticket", admin: { position: "sidebar" } },
|
|
5833
5867
|
{ name: "slaPolicy", type: "relationship", relationTo: slugs.slaPolicies, label: "Politique SLA", admin: { position: "sidebar" } },
|
|
5834
5868
|
{
|
|
5835
5869
|
name: "category",
|
|
@@ -5881,7 +5915,17 @@ function createTicketsCollection(slugs, options) {
|
|
|
5881
5915
|
{ name: "snoozeUntil", type: "date", label: "Snooze jusqu'au", admin: { position: "sidebar", date: { pickerAppearance: "dayAndTime", displayFormat: "dd/MM/yyyy HH:mm" } } },
|
|
5882
5916
|
// Billing sidebar
|
|
5883
5917
|
{ name: "billable", type: "checkbox", defaultValue: true, label: "Facturable", admin: { position: "sidebar" } },
|
|
5884
|
-
{
|
|
5918
|
+
{
|
|
5919
|
+
name: "showTimeToClient",
|
|
5920
|
+
type: "checkbox",
|
|
5921
|
+
defaultValue: true,
|
|
5922
|
+
label: "Afficher le temps au client",
|
|
5923
|
+
admin: {
|
|
5924
|
+
position: "sidebar",
|
|
5925
|
+
description: "Auto-d\xE9sactiv\xE9 en mode forfait",
|
|
5926
|
+
condition: (data) => data?.billingType !== "flat"
|
|
5927
|
+
}
|
|
5928
|
+
},
|
|
5885
5929
|
{ name: "totalTimeMinutes", type: "number", defaultValue: 0, label: "Temps total (minutes)", admin: { readOnly: true, position: "sidebar" } }
|
|
5886
5930
|
],
|
|
5887
5931
|
hooks: {
|
package/dist/index.js
CHANGED
|
@@ -274,9 +274,18 @@ R\xE9dige une r\xE9ponse appropri\xE9e au dernier message du client. Sois concis
|
|
|
274
274
|
if (!aiSettings.enableRewrite) {
|
|
275
275
|
return Response.json({ rewritten: "", disabled: true });
|
|
276
276
|
}
|
|
277
|
-
const { text } = body;
|
|
277
|
+
const { text, style } = body;
|
|
278
278
|
if (!text?.trim()) return Response.json({ error: "text required" }, { status: 400 });
|
|
279
|
-
const
|
|
279
|
+
const styleInstructions = {
|
|
280
|
+
auto: "Garde le m\xEAme ton (tutoiement/vouvoiement).",
|
|
281
|
+
tutoyer: "Utilise le tutoiement. Si le texte vouvoie, convertis en tutoiement.",
|
|
282
|
+
vouvoyer: "Utilise le vouvoiement. Si le texte tutoie, convertis en vouvoiement.",
|
|
283
|
+
formel: "Adopte un ton formel et professionnel avec vouvoiement.",
|
|
284
|
+
court: "Raccourcis le texte au maximum tout en gardant le sens. Sois concis et direct.",
|
|
285
|
+
amical: "Adopte un ton chaleureux et amical avec tutoiement."
|
|
286
|
+
};
|
|
287
|
+
const styleGuide = styleInstructions[style || "auto"] || styleInstructions.auto;
|
|
288
|
+
const prompt = `Tu es un agent de support technique professionnel. Reformule le texte ci-dessous de mani\xE8re plus professionnelle et corrige les fautes d'orthographe/grammaire. ${styleGuide} Ne change pas le fond du message, am\xE9liore uniquement la forme. R\xE9ponds UNIQUEMENT avec le texte reformul\xE9, sans commentaire ni explication.
|
|
280
289
|
|
|
281
290
|
Texte original :
|
|
282
291
|
${text}`;
|
|
@@ -5666,6 +5675,37 @@ function createTicketsCollection(slugs, options) {
|
|
|
5666
5675
|
});
|
|
5667
5676
|
}
|
|
5668
5677
|
const billingFields = [
|
|
5678
|
+
{
|
|
5679
|
+
type: "row",
|
|
5680
|
+
fields: [
|
|
5681
|
+
{
|
|
5682
|
+
name: "billingType",
|
|
5683
|
+
type: "select",
|
|
5684
|
+
label: "Type de facturation",
|
|
5685
|
+
defaultValue: "hourly",
|
|
5686
|
+
options: [
|
|
5687
|
+
{ label: "Au temps pass\xE9", value: "hourly" },
|
|
5688
|
+
{ label: "Forfait", value: "flat" }
|
|
5689
|
+
],
|
|
5690
|
+
admin: { width: "33%" }
|
|
5691
|
+
},
|
|
5692
|
+
{
|
|
5693
|
+
name: "flatRateAmount",
|
|
5694
|
+
type: "number",
|
|
5695
|
+
label: "Montant forfait (EUR)",
|
|
5696
|
+
admin: {
|
|
5697
|
+
width: "33%",
|
|
5698
|
+
condition: (data) => data?.billingType === "flat"
|
|
5699
|
+
}
|
|
5700
|
+
},
|
|
5701
|
+
{
|
|
5702
|
+
name: "billedAmount",
|
|
5703
|
+
type: "number",
|
|
5704
|
+
label: "Montant factur\xE9 (EUR)",
|
|
5705
|
+
admin: { width: "33%" }
|
|
5706
|
+
}
|
|
5707
|
+
]
|
|
5708
|
+
},
|
|
5669
5709
|
{
|
|
5670
5710
|
type: "row",
|
|
5671
5711
|
fields: [
|
|
@@ -5706,14 +5746,8 @@ function createTicketsCollection(slugs, options) {
|
|
|
5706
5746
|
{
|
|
5707
5747
|
name: "paidAt",
|
|
5708
5748
|
type: "date",
|
|
5709
|
-
label: "
|
|
5749
|
+
label: "Pay\xE9 le",
|
|
5710
5750
|
admin: { width: "33%", date: { displayFormat: "dd/MM/yyyy HH:mm" } }
|
|
5711
|
-
},
|
|
5712
|
-
{
|
|
5713
|
-
name: "billedAmount",
|
|
5714
|
-
type: "number",
|
|
5715
|
-
label: "Montant facture (EUR)",
|
|
5716
|
-
admin: { width: "33%" }
|
|
5717
5751
|
}
|
|
5718
5752
|
]
|
|
5719
5753
|
}
|
|
@@ -5823,7 +5857,7 @@ function createTicketsCollection(slugs, options) {
|
|
|
5823
5857
|
]
|
|
5824
5858
|
},
|
|
5825
5859
|
// Sidebar
|
|
5826
|
-
{ name: "ticketNumber", type: "text", unique: true, label: "N\xB0 Ticket", admin: {
|
|
5860
|
+
{ name: "ticketNumber", type: "text", unique: true, label: "N\xB0 Ticket", admin: { position: "sidebar" } },
|
|
5827
5861
|
{ name: "slaPolicy", type: "relationship", relationTo: slugs.slaPolicies, label: "Politique SLA", admin: { position: "sidebar" } },
|
|
5828
5862
|
{
|
|
5829
5863
|
name: "category",
|
|
@@ -5875,7 +5909,17 @@ function createTicketsCollection(slugs, options) {
|
|
|
5875
5909
|
{ name: "snoozeUntil", type: "date", label: "Snooze jusqu'au", admin: { position: "sidebar", date: { pickerAppearance: "dayAndTime", displayFormat: "dd/MM/yyyy HH:mm" } } },
|
|
5876
5910
|
// Billing sidebar
|
|
5877
5911
|
{ name: "billable", type: "checkbox", defaultValue: true, label: "Facturable", admin: { position: "sidebar" } },
|
|
5878
|
-
{
|
|
5912
|
+
{
|
|
5913
|
+
name: "showTimeToClient",
|
|
5914
|
+
type: "checkbox",
|
|
5915
|
+
defaultValue: true,
|
|
5916
|
+
label: "Afficher le temps au client",
|
|
5917
|
+
admin: {
|
|
5918
|
+
position: "sidebar",
|
|
5919
|
+
description: "Auto-d\xE9sactiv\xE9 en mode forfait",
|
|
5920
|
+
condition: (data) => data?.billingType !== "flat"
|
|
5921
|
+
}
|
|
5922
|
+
},
|
|
5879
5923
|
{ name: "totalTimeMinutes", type: "number", defaultValue: 0, label: "Temps total (minutes)", admin: { readOnly: true, position: "sidebar" } }
|
|
5880
5924
|
],
|
|
5881
5925
|
hooks: {
|
package/package.json
CHANGED
|
@@ -458,6 +458,37 @@ export function createTicketsCollection(slugs: CollectionSlugs, options?: {
|
|
|
458
458
|
|
|
459
459
|
// Billing collapsible fields
|
|
460
460
|
const billingFields: Field[] = [
|
|
461
|
+
{
|
|
462
|
+
type: 'row',
|
|
463
|
+
fields: [
|
|
464
|
+
{
|
|
465
|
+
name: 'billingType',
|
|
466
|
+
type: 'select',
|
|
467
|
+
label: 'Type de facturation',
|
|
468
|
+
defaultValue: 'hourly',
|
|
469
|
+
options: [
|
|
470
|
+
{ label: 'Au temps passé', value: 'hourly' },
|
|
471
|
+
{ label: 'Forfait', value: 'flat' },
|
|
472
|
+
],
|
|
473
|
+
admin: { width: '33%' },
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: 'flatRateAmount',
|
|
477
|
+
type: 'number',
|
|
478
|
+
label: 'Montant forfait (EUR)',
|
|
479
|
+
admin: {
|
|
480
|
+
width: '33%',
|
|
481
|
+
condition: (data) => data?.billingType === 'flat',
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'billedAmount',
|
|
486
|
+
type: 'number',
|
|
487
|
+
label: 'Montant facturé (EUR)',
|
|
488
|
+
admin: { width: '33%' },
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
},
|
|
461
492
|
{
|
|
462
493
|
type: 'row',
|
|
463
494
|
fields: [
|
|
@@ -498,15 +529,9 @@ export function createTicketsCollection(slugs: CollectionSlugs, options?: {
|
|
|
498
529
|
{
|
|
499
530
|
name: 'paidAt',
|
|
500
531
|
type: 'date',
|
|
501
|
-
label: '
|
|
532
|
+
label: 'Payé le',
|
|
502
533
|
admin: { width: '33%', date: { displayFormat: 'dd/MM/yyyy HH:mm' } },
|
|
503
534
|
},
|
|
504
|
-
{
|
|
505
|
-
name: 'billedAmount',
|
|
506
|
-
type: 'number',
|
|
507
|
-
label: 'Montant facture (EUR)',
|
|
508
|
-
admin: { width: '33%' },
|
|
509
|
-
},
|
|
510
535
|
],
|
|
511
536
|
},
|
|
512
537
|
]
|
|
@@ -607,7 +632,7 @@ export function createTicketsCollection(slugs: CollectionSlugs, options?: {
|
|
|
607
632
|
],
|
|
608
633
|
},
|
|
609
634
|
// Sidebar
|
|
610
|
-
{ name: 'ticketNumber', type: 'text', unique: true, label: 'N° Ticket', admin: {
|
|
635
|
+
{ name: 'ticketNumber', type: 'text', unique: true, label: 'N° Ticket', admin: { position: 'sidebar' } },
|
|
611
636
|
{ name: 'slaPolicy', type: 'relationship', relationTo: slugs.slaPolicies, label: 'Politique SLA', admin: { position: 'sidebar' } },
|
|
612
637
|
{
|
|
613
638
|
name: 'category', type: 'select', label: 'Categorie',
|
|
@@ -651,7 +676,17 @@ export function createTicketsCollection(slugs: CollectionSlugs, options?: {
|
|
|
651
676
|
{ name: 'snoozeUntil', type: 'date', label: 'Snooze jusqu\'au', admin: { position: 'sidebar', date: { pickerAppearance: 'dayAndTime', displayFormat: 'dd/MM/yyyy HH:mm' } } },
|
|
652
677
|
// Billing sidebar
|
|
653
678
|
{ name: 'billable', type: 'checkbox', defaultValue: true, label: 'Facturable', admin: { position: 'sidebar' } },
|
|
654
|
-
{
|
|
679
|
+
{
|
|
680
|
+
name: 'showTimeToClient',
|
|
681
|
+
type: 'checkbox',
|
|
682
|
+
defaultValue: true,
|
|
683
|
+
label: 'Afficher le temps au client',
|
|
684
|
+
admin: {
|
|
685
|
+
position: 'sidebar',
|
|
686
|
+
description: 'Auto-désactivé en mode forfait',
|
|
687
|
+
condition: (data) => data?.billingType !== 'flat',
|
|
688
|
+
},
|
|
689
|
+
},
|
|
655
690
|
{ name: 'totalTimeMinutes', type: 'number', defaultValue: 0, label: 'Temps total (minutes)', admin: { readOnly: true, position: 'sidebar' } },
|
|
656
691
|
],
|
|
657
692
|
hooks: {
|
|
@@ -87,7 +87,7 @@ export function useAI(
|
|
|
87
87
|
setAiReplying(false)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const handleAiRewrite = async () => {
|
|
90
|
+
const handleAiRewrite = async (style: string = 'auto') => {
|
|
91
91
|
if (!replyBody.trim()) return
|
|
92
92
|
setAiRewriting(true)
|
|
93
93
|
try {
|
|
@@ -95,7 +95,7 @@ export function useAI(
|
|
|
95
95
|
method: 'POST',
|
|
96
96
|
headers: { 'Content-Type': 'application/json' },
|
|
97
97
|
credentials: 'include',
|
|
98
|
-
body: JSON.stringify({ action: 'rewrite', text: replyBody }),
|
|
98
|
+
body: JSON.stringify({ action: 'rewrite', text: replyBody, style }),
|
|
99
99
|
})
|
|
100
100
|
if (res.ok) {
|
|
101
101
|
const data = await res.json()
|
|
@@ -82,6 +82,98 @@ const layoutStyles = {
|
|
|
82
82
|
} as React.CSSProperties,
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// ── Rewrite style dropdown ──────────────────────────────────────────────
|
|
86
|
+
const REWRITE_STYLES = [
|
|
87
|
+
{ id: 'auto', label: '✏️ Auto', desc: 'Garde le ton actuel' },
|
|
88
|
+
{ id: 'tutoyer', label: '👋 Tutoyer', desc: 'Passe en tu' },
|
|
89
|
+
{ id: 'vouvoyer', label: '🎩 Vouvoyer', desc: 'Passe en vous' },
|
|
90
|
+
{ id: 'formel', label: '💼 Formel', desc: 'Ton professionnel' },
|
|
91
|
+
{ id: 'amical', label: '😊 Amical', desc: 'Ton chaleureux' },
|
|
92
|
+
{ id: 'court', label: '⚡ Court', desc: 'Version concise' },
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
const RewriteDropdown: React.FC<{
|
|
96
|
+
disabled: boolean
|
|
97
|
+
loading: boolean
|
|
98
|
+
onSelect: (style: string) => void
|
|
99
|
+
}> = ({ disabled, loading, onSelect }) => {
|
|
100
|
+
const [open, setOpen] = useState(false)
|
|
101
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
102
|
+
|
|
103
|
+
React.useEffect(() => {
|
|
104
|
+
if (!open) return
|
|
105
|
+
const close = (e: MouseEvent) => {
|
|
106
|
+
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
|
|
107
|
+
}
|
|
108
|
+
document.addEventListener('mousedown', close)
|
|
109
|
+
return () => document.removeEventListener('mousedown', close)
|
|
110
|
+
}, [open])
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div ref={ref} style={{ position: 'relative', display: 'inline-block' }}>
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => setOpen(!open)}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
style={{
|
|
119
|
+
...s.outlineBtn('#0891b2', disabled),
|
|
120
|
+
fontSize: '11px',
|
|
121
|
+
padding: '3px 10px',
|
|
122
|
+
borderRadius: '14px',
|
|
123
|
+
display: 'flex',
|
|
124
|
+
alignItems: 'center',
|
|
125
|
+
gap: '4px',
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
{loading ? 'Reformulation...' : '✏️ Reformuler'}
|
|
129
|
+
{!loading && <span style={{ fontSize: 9, opacity: 0.7 }}>▼</span>}
|
|
130
|
+
</button>
|
|
131
|
+
{open && !disabled && !loading && (
|
|
132
|
+
<div
|
|
133
|
+
style={{
|
|
134
|
+
position: 'absolute',
|
|
135
|
+
bottom: '100%',
|
|
136
|
+
left: 0,
|
|
137
|
+
marginBottom: 4,
|
|
138
|
+
background: 'var(--theme-elevation-0, #fff)',
|
|
139
|
+
border: '1px solid var(--theme-elevation-200, #e5e7eb)',
|
|
140
|
+
borderRadius: 8,
|
|
141
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.12)',
|
|
142
|
+
zIndex: 100,
|
|
143
|
+
minWidth: 180,
|
|
144
|
+
overflow: 'hidden',
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{REWRITE_STYLES.map((style) => (
|
|
148
|
+
<button
|
|
149
|
+
key={style.id}
|
|
150
|
+
type="button"
|
|
151
|
+
onClick={() => { setOpen(false); onSelect(style.id) }}
|
|
152
|
+
style={{
|
|
153
|
+
display: 'flex',
|
|
154
|
+
flexDirection: 'column',
|
|
155
|
+
width: '100%',
|
|
156
|
+
padding: '8px 12px',
|
|
157
|
+
border: 'none',
|
|
158
|
+
background: 'transparent',
|
|
159
|
+
cursor: 'pointer',
|
|
160
|
+
textAlign: 'left',
|
|
161
|
+
borderBottom: '1px solid var(--theme-elevation-100, #f3f4f6)',
|
|
162
|
+
transition: 'background 120ms',
|
|
163
|
+
}}
|
|
164
|
+
onMouseEnter={(e) => { (e.target as HTMLElement).style.background = 'var(--theme-elevation-50, #f9fafb)' }}
|
|
165
|
+
onMouseLeave={(e) => { (e.target as HTMLElement).style.background = 'transparent' }}
|
|
166
|
+
>
|
|
167
|
+
<span style={{ fontSize: 12, fontWeight: 600, color: 'var(--theme-text, #111)' }}>{style.label}</span>
|
|
168
|
+
<span style={{ fontSize: 10, color: 'var(--theme-elevation-500, #6b7280)' }}>{style.desc}</span>
|
|
169
|
+
</button>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
85
177
|
const TicketConversation: React.FC = () => {
|
|
86
178
|
const { id } = useDocumentIdFromUrl()
|
|
87
179
|
const [features] = useState<TicketingFeatures>(() => getFeatures())
|
|
@@ -836,13 +928,11 @@ const TicketConversation: React.FC = () => {
|
|
|
836
928
|
</button>
|
|
837
929
|
)}
|
|
838
930
|
{features.ai && (
|
|
839
|
-
<
|
|
840
|
-
onClick={handleAiRewrite}
|
|
931
|
+
<RewriteDropdown
|
|
841
932
|
disabled={aiRewriting || !replyBody.trim()}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
</button>
|
|
933
|
+
loading={aiRewriting}
|
|
934
|
+
onSelect={(style) => handleAiRewrite(style)}
|
|
935
|
+
/>
|
|
846
936
|
)}
|
|
847
937
|
<CodeBlockInserter
|
|
848
938
|
style={{ ...s.outlineBtn('#059669', false), fontSize: '11px', padding: '3px 10px', borderRadius: '14px' }}
|
package/src/endpoints/ai.ts
CHANGED
|
@@ -169,10 +169,20 @@ Rédige une réponse appropriée au dernier message du client. Sois concis (3-5
|
|
|
169
169
|
if (!aiSettings.enableRewrite) {
|
|
170
170
|
return Response.json({ rewritten: '', disabled: true })
|
|
171
171
|
}
|
|
172
|
-
const { text } = body as { text: string }
|
|
172
|
+
const { text, style } = body as { text: string; style?: string }
|
|
173
173
|
if (!text?.trim()) return Response.json({ error: 'text required' }, { status: 400 })
|
|
174
174
|
|
|
175
|
-
const
|
|
175
|
+
const styleInstructions: Record<string, string> = {
|
|
176
|
+
auto: 'Garde le même ton (tutoiement/vouvoiement).',
|
|
177
|
+
tutoyer: 'Utilise le tutoiement. Si le texte vouvoie, convertis en tutoiement.',
|
|
178
|
+
vouvoyer: 'Utilise le vouvoiement. Si le texte tutoie, convertis en vouvoiement.',
|
|
179
|
+
formel: 'Adopte un ton formel et professionnel avec vouvoiement.',
|
|
180
|
+
court: 'Raccourcis le texte au maximum tout en gardant le sens. Sois concis et direct.',
|
|
181
|
+
amical: 'Adopte un ton chaleureux et amical avec tutoiement.',
|
|
182
|
+
}
|
|
183
|
+
const styleGuide = styleInstructions[style || 'auto'] || styleInstructions.auto
|
|
184
|
+
|
|
185
|
+
const prompt = `Tu es un agent de support technique professionnel. Reformule le texte ci-dessous de manière plus professionnelle et corrige les fautes d'orthographe/grammaire. ${styleGuide} Ne change pas le fond du message, améliore uniquement la forme. Réponds UNIQUEMENT avec le texte reformulé, sans commentaire ni explication.
|
|
176
186
|
|
|
177
187
|
Texte original :
|
|
178
188
|
${text}`
|