@workspace-platform/ui 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,1841 @@
1
+ // src/provider.tsx
2
+ import { createContext, useContext, useMemo } from "react";
3
+
4
+ // src/api/client.ts
5
+ function createWorkspaceClient(config) {
6
+ function buildHeaders(extra) {
7
+ const base = {
8
+ "Content-Type": "application/json",
9
+ ...extra
10
+ };
11
+ const token = config.getAuthToken();
12
+ if (token) base["Authorization"] = `Bearer ${token}`;
13
+ if (config.extraHeaders) Object.assign(base, config.extraHeaders);
14
+ return base;
15
+ }
16
+ async function req(path, options) {
17
+ const res = await fetch(`${config.baseUrl}${path}`, {
18
+ ...options,
19
+ headers: buildHeaders(options?.headers)
20
+ });
21
+ if (res.status === 204) return void 0;
22
+ const json = await res.json();
23
+ if (!res.ok) {
24
+ const rawMessage = json?.message ?? `Error ${res.status}`;
25
+ const errorMessage = Array.isArray(rawMessage) ? rawMessage.join(", ") : typeof rawMessage === "string" ? rawMessage : JSON.stringify(rawMessage);
26
+ throw new Error(errorMessage);
27
+ }
28
+ return json;
29
+ }
30
+ async function reqBlob(path) {
31
+ const res = await fetch(`${config.baseUrl}${path}`, {
32
+ headers: buildHeaders()
33
+ });
34
+ if (!res.ok) {
35
+ const text = await res.text().catch(() => "");
36
+ throw new Error(`Error ${res.status}: ${text}`);
37
+ }
38
+ return res.blob();
39
+ }
40
+ async function upload(path, formData) {
41
+ const headers = {};
42
+ const token = config.getAuthToken();
43
+ if (token) headers["Authorization"] = `Bearer ${token}`;
44
+ if (config.extraHeaders) Object.assign(headers, config.extraHeaders);
45
+ const res = await fetch(`${config.baseUrl}${path}`, {
46
+ method: "POST",
47
+ headers,
48
+ body: formData
49
+ });
50
+ if (!res.ok) {
51
+ const json = await res.json().catch(() => ({}));
52
+ throw new Error(
53
+ json?.message ?? `Upload failed (${res.status})`
54
+ );
55
+ }
56
+ return res.json();
57
+ }
58
+ return {
59
+ get: (path) => req(path),
60
+ post: (path, body) => req(path, { method: "POST", body: JSON.stringify(body) }),
61
+ patch: (path, body) => req(path, { method: "PATCH", body: JSON.stringify(body) }),
62
+ delete: (path) => req(path, { method: "DELETE" }),
63
+ upload: (path, formData) => upload(path, formData),
64
+ getBlob: (path) => reqBlob(path)
65
+ };
66
+ }
67
+
68
+ // src/socket/client.ts
69
+ import { io } from "socket.io-client";
70
+ function createWorkspaceSocket(config) {
71
+ let socket = null;
72
+ function getSocket() {
73
+ if (!socket) {
74
+ const token = config.getAuthToken();
75
+ socket = io(`${config.baseUrl}/workspaces`, {
76
+ autoConnect: false,
77
+ auth: { token: token ?? "" }
78
+ });
79
+ }
80
+ return socket;
81
+ }
82
+ function connect(userId) {
83
+ const s = getSocket();
84
+ if (!s.connected) s.connect();
85
+ const uid = userId ?? config.currentUserId;
86
+ if (uid) {
87
+ if (s.connected) {
88
+ s.emit("authenticate", { userId: uid });
89
+ } else {
90
+ s.once("connect", () => {
91
+ s.emit("authenticate", { userId: uid });
92
+ });
93
+ }
94
+ }
95
+ return s;
96
+ }
97
+ function disconnect() {
98
+ socket?.disconnect();
99
+ socket = null;
100
+ }
101
+ return { getSocket, connect, disconnect };
102
+ }
103
+
104
+ // src/provider.tsx
105
+ import { jsx } from "react/jsx-runtime";
106
+ var WorkspaceContext = createContext(null);
107
+ function WorkspaceProvider({
108
+ config,
109
+ children
110
+ }) {
111
+ const value = useMemo(() => {
112
+ const apiClient = createWorkspaceClient(config);
113
+ const socket = createWorkspaceSocket(config);
114
+ return { config, apiClient, socket };
115
+ }, [config.baseUrl, config.currentUserId]);
116
+ return /* @__PURE__ */ jsx(WorkspaceContext.Provider, { value, children });
117
+ }
118
+ function useWorkspaceContext() {
119
+ const ctx = useContext(WorkspaceContext);
120
+ if (!ctx)
121
+ throw new Error(
122
+ "useWorkspaceContext must be used within <WorkspaceProvider>"
123
+ );
124
+ return ctx;
125
+ }
126
+
127
+ // src/types/index.ts
128
+ var WorkspaceEvent = /* @__PURE__ */ ((WorkspaceEvent2) => {
129
+ WorkspaceEvent2["WORKSPACE_CREATED"] = "workspace.created";
130
+ WorkspaceEvent2["WORKSPACE_UPDATED"] = "workspace.updated";
131
+ WorkspaceEvent2["WORKSPACE_DELETED"] = "workspace.deleted";
132
+ WorkspaceEvent2["WORKSPACE_STATUS_CHANGED"] = "workspace.statusChanged";
133
+ WorkspaceEvent2["WORKSPACE_ADDED"] = "workspace.added";
134
+ WorkspaceEvent2["WORKSPACE_REMOVED"] = "workspace.removed";
135
+ WorkspaceEvent2["WORKSPACE_MEMBER_ADDED"] = "workspace.member.added";
136
+ WorkspaceEvent2["WORKSPACE_MEMBER_UPDATED"] = "workspace.member.updated";
137
+ WorkspaceEvent2["WORKSPACE_MEMBER_REMOVED"] = "workspace.member.removed";
138
+ WorkspaceEvent2["WORKSPACE_MESSAGE_CREATED"] = "workspace.message.created";
139
+ WorkspaceEvent2["WORKSPACE_MESSAGE_UPDATED"] = "workspace.message.updated";
140
+ WorkspaceEvent2["WORKSPACE_MESSAGE_DELETED"] = "workspace.message.deleted";
141
+ WorkspaceEvent2["WORKSPACE_MEMBER_USER_MENTIONED"] = "workspace.member.user.mentioned";
142
+ WorkspaceEvent2["WORKSPACE_MEMBER_AGENT_MENTIONED"] = "workspace.member.agent.mentioned";
143
+ WorkspaceEvent2["WORKSPACE_ARTEFACT_CREATED"] = "workspace.artefact.created";
144
+ WorkspaceEvent2["WORKSPACE_TYPING_STARTED"] = "workspace.typing.started";
145
+ WorkspaceEvent2["WORKSPACE_TYPING_STOPPED"] = "workspace.typing.stopped";
146
+ WorkspaceEvent2["WORKSPACE_MESSAGE_REACTION_CREATED"] = "workspace.message.reaction.created";
147
+ WorkspaceEvent2["WORKSPACE_MESSAGE_REACTION_DELETED"] = "workspace.message.reaction.deleted";
148
+ WorkspaceEvent2["WORKSPACE_MESSAGE_PINNED"] = "workspace.message.pinned";
149
+ WorkspaceEvent2["PRIVATE_WORKSPACE_CREATED"] = "private.workspace.created";
150
+ WorkspaceEvent2["PRIVATE_WORKSPACE_MESSAGE_CREATED"] = "private.workspace.message.created";
151
+ WorkspaceEvent2["PRIVATE_WORKSPACE_MEMBER_ADDED"] = "private.workspace.member.added";
152
+ return WorkspaceEvent2;
153
+ })(WorkspaceEvent || {});
154
+
155
+ // src/types/activity.ts
156
+ var FILTER_PILLS = [
157
+ {
158
+ label: "Tout",
159
+ value: void 0,
160
+ entityType: void 0,
161
+ category: void 0
162
+ },
163
+ {
164
+ label: "Contexte",
165
+ value: "context",
166
+ entityType: "workspaces",
167
+ category: "context"
168
+ },
169
+ {
170
+ label: "Membres",
171
+ value: "members",
172
+ entityType: "workspace_members",
173
+ category: "members"
174
+ },
175
+ {
176
+ label: "Artefacts",
177
+ value: "artefacts",
178
+ entityType: "workspace_artefacts",
179
+ category: "artefacts"
180
+ }
181
+ ];
182
+ var WORKSPACE_FIELD_LABELS = {
183
+ name: "Nom",
184
+ type: "Type",
185
+ scope: "P\xE9rim\xE8tre",
186
+ entities: "Entit\xE9s",
187
+ entitiesWithSecrets: "Entit\xE9s",
188
+ status: "Statut",
189
+ description: "Description"
190
+ };
191
+ var WORKSPACE_STATUS_LABELS = {
192
+ ACTIVE: "Actif",
193
+ ARCHIVED: "Archiv\xE9",
194
+ DRAFT: "Brouillon",
195
+ CLOSED: "Cl\xF4tur\xE9"
196
+ };
197
+ var MEMBER_ROLE_LABELS = {
198
+ owner: "Propri\xE9taire",
199
+ participant: "Participant",
200
+ viewer: "Lecteur"
201
+ };
202
+ var IGNORED_FIELDS = [
203
+ "_id",
204
+ "__v",
205
+ "updatedAt",
206
+ "createdAt",
207
+ "createdBy",
208
+ "updatedBy",
209
+ "workspace"
210
+ ];
211
+ var CATEGORY_STYLES = {
212
+ context: {
213
+ dot: "bg-primary/15 border-primary/30",
214
+ badge: "bg-primary/10 text-primary",
215
+ label: "Contexte"
216
+ },
217
+ members: {
218
+ dot: "bg-info/15 border-info/30",
219
+ badge: "bg-info/10 text-info",
220
+ label: "Membre"
221
+ },
222
+ artefacts: {
223
+ dot: "bg-success/15 border-success/30",
224
+ badge: "bg-success/10 text-success",
225
+ label: "Artefact"
226
+ },
227
+ other: {
228
+ dot: "bg-border",
229
+ badge: "bg-border text-text-muted",
230
+ label: "Autre"
231
+ }
232
+ };
233
+ var ICON_COLOR = {
234
+ created: "text-primary",
235
+ updated: "text-primary",
236
+ deleted: "text-danger",
237
+ member_added: "text-info",
238
+ member_removed: "text-danger",
239
+ member_role: "text-info",
240
+ artefact_added: "text-success",
241
+ artefact_deleted: "text-danger",
242
+ archived: "text-text-muted",
243
+ generic: "text-text-muted"
244
+ };
245
+
246
+ // src/api/index.ts
247
+ function createWorkspaceApis(client) {
248
+ const workspaceTypeApi = {
249
+ list: () => client.get("/workspace-types"),
250
+ get: (id) => client.get(`/workspace-types/${id}`),
251
+ create: (payload) => client.post("/workspace-types", payload),
252
+ update: (id, payload) => client.patch(`/workspace-types/${id}`, payload),
253
+ delete: (id) => client.delete(`/workspace-types/${id}`)
254
+ };
255
+ const workspaceApi = {
256
+ list: (params) => {
257
+ const cleanParams = Object.fromEntries(
258
+ Object.entries(params || {}).filter(([, v]) => v != null && v !== "" && v !== "all").map(([k, v]) => [k, String(v)])
259
+ );
260
+ const qs = Object.keys(cleanParams).length > 0 ? "?" + new URLSearchParams(cleanParams).toString() : "";
261
+ return client.get(`/workspaces${qs}`);
262
+ },
263
+ get: (id) => client.get(`/workspaces/${id}`),
264
+ create: (payload) => client.post("/workspaces", payload),
265
+ update: (id, payload) => client.patch(`/workspaces/${id}`, payload),
266
+ updateStatus: (id, status) => client.patch(`/workspaces/${id}/status`, { status }),
267
+ delete: (id) => client.delete(`/workspaces/${id}`),
268
+ startTyping: (id) => client.post(`/workspaces/${id}/writing/start`, {}),
269
+ stopTyping: (id) => client.post(`/workspaces/${id}/writing/stop`, {}),
270
+ timeline: (workspaceId, params) => {
271
+ const qs = params ? "?" + new URLSearchParams(
272
+ Object.fromEntries(
273
+ Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
274
+ )
275
+ ).toString() : "";
276
+ return client.get(`/workspaces/${workspaceId}/timeline${qs}`);
277
+ }
278
+ };
279
+ const memberApi = {
280
+ list: (workspaceId) => client.get(`/workspaces/${workspaceId}/members`),
281
+ add: (workspaceId, payload) => client.post(`/workspaces/${workspaceId}/members`, payload),
282
+ updateRole: (workspaceId, memberId, role) => client.patch(`/workspaces/${workspaceId}/members/${memberId}`, { role }),
283
+ remove: (workspaceId, memberId) => client.delete(`/workspaces/${workspaceId}/members/${memberId}`)
284
+ };
285
+ const messageApi = {
286
+ list: (workspaceId, params) => {
287
+ const qs = params ? "?" + new URLSearchParams(
288
+ Object.fromEntries(
289
+ Object.entries(params).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)])
290
+ )
291
+ ).toString() : "";
292
+ return client.get(`/workspaces/${workspaceId}/messages${qs}`);
293
+ },
294
+ send: (workspaceId, payload) => client.post(`/workspaces/${workspaceId}/messages`, payload),
295
+ update: (workspaceId, messageId, payload) => client.patch(`/workspaces/${workspaceId}/messages/${messageId}`, payload),
296
+ delete: (workspaceId, messageId) => client.delete(`/workspaces/${workspaceId}/messages/${messageId}`),
297
+ togglePin: (workspaceId, messageId) => client.patch(`/workspaces/${workspaceId}/messages/${messageId}/pin`, {})
298
+ };
299
+ const artefactApi = {
300
+ upload: async (workspaceId, files, messageId, artefactType) => {
301
+ const formData = new FormData();
302
+ files.forEach((f) => formData.append("files", f));
303
+ if (messageId) formData.append("messageId", messageId);
304
+ if (artefactType) formData.append("type", artefactType);
305
+ return client.upload(`/workspaces/${workspaceId}/artefacts`, formData);
306
+ },
307
+ list: (workspaceId, artefactType) => {
308
+ const query = artefactType ? `?type=${artefactType}` : "";
309
+ return client.get(
310
+ `/workspaces/${workspaceId}/artefacts${query}`
311
+ );
312
+ },
313
+ listPaginated: (workspaceId, params) => {
314
+ const entries = [];
315
+ if (params) {
316
+ if (params.type) entries.push(["type", params.type]);
317
+ if (params.limit != null) entries.push(["limit", String(params.limit)]);
318
+ if (params.offset != null) entries.push(["offset", String(params.offset)]);
319
+ }
320
+ const qs = entries.length > 0 ? "?" + new URLSearchParams(entries).toString() : "";
321
+ return client.get(
322
+ `/workspaces/${workspaceId}/artefacts${qs}`
323
+ );
324
+ },
325
+ delete: (workspaceId, artefactId) => client.delete(`/workspaces/${workspaceId}/artefacts/${artefactId}`),
326
+ getSignedUrl: (fileId) => client.get(`/files/${fileId}/signed-url`),
327
+ getDownloadBlob: (fileId) => client.getBlob(`/files/${fileId}/download`),
328
+ messagesWithArtefacts: (workspaceId, params) => {
329
+ const entries = [];
330
+ if (params) {
331
+ if (params.limit != null) entries.push(["limit", String(params.limit)]);
332
+ if (params.offset != null) entries.push(["offset", String(params.offset)]);
333
+ if (params.sort) entries.push(["sort", params.sort]);
334
+ if (params.members && params.members.length > 0)
335
+ entries.push(["members", params.members.join(",")]);
336
+ if (params.concernsMe) entries.push(["concernsMe", "true"]);
337
+ if (params.pinned) entries.push(["pinned", "true"]);
338
+ }
339
+ const qs = entries.length > 0 ? "?" + new URLSearchParams(entries).toString() : "";
340
+ return client.get(
341
+ `/workspaces/${workspaceId}/messages/with-artefacts${qs}`
342
+ );
343
+ }
344
+ };
345
+ const reactionApi = {
346
+ toggle: (workspaceId, messageId, reaction) => client.post(
347
+ `/workspaces/${workspaceId}/messages/${messageId}/reactions/toggle`,
348
+ { reaction }
349
+ )
350
+ };
351
+ const agentApi = {
352
+ list: (search) => {
353
+ const qs = search ? `?search=${encodeURIComponent(search)}` : "";
354
+ return client.get(`/workspace-agents${qs}`);
355
+ },
356
+ create: (body) => client.post("/workspace-agents", body),
357
+ update: (id, body) => client.patch(`/workspace-agents/${id}`, body),
358
+ delete: (id) => client.delete(`/workspace-agents/${id}`),
359
+ getDeleteInfo: (id) => client.get(`/workspace-agents/${id}/delete-info`)
360
+ };
361
+ const summaryApi = {
362
+ generate: (workspaceId) => client.post(`/workspaces/${workspaceId}/summaries/generate`, {}),
363
+ getLatest: (workspaceId) => client.get(`/workspaces/${workspaceId}/summaries/latest`),
364
+ getHistory: (workspaceId) => client.get(`/workspaces/${workspaceId}/summaries`),
365
+ create: (workspaceId, payload) => client.post(`/workspaces/${workspaceId}/summaries`, payload),
366
+ update: (workspaceId, summaryId, payload) => client.patch(
367
+ `/workspaces/${workspaceId}/summaries/${summaryId}`,
368
+ payload
369
+ )
370
+ };
371
+ const webhookApi = {
372
+ list: (params) => {
373
+ const cleanParams = Object.fromEntries(
374
+ Object.entries(params || {}).filter(([, v]) => v !== void 0 && v !== "")
375
+ );
376
+ const qs = Object.keys(cleanParams).length > 0 ? "?" + new URLSearchParams(cleanParams).toString() : "";
377
+ return client.get(`/webhook-subscriptions${qs}`);
378
+ },
379
+ create: (data) => client.post("/webhook-subscriptions", data),
380
+ update: (id, data) => client.patch(`/webhook-subscriptions/${id}`, data),
381
+ delete: (id) => client.delete(`/webhook-subscriptions/${id}`)
382
+ };
383
+ const syncApi = {
384
+ getAvailableProviders: () => client.get("/workspaces/sync/providers").then((res) => res.providers),
385
+ getStatus: (workspaceId) => client.get(`/workspaces/${workspaceId}/sync-status`),
386
+ toggleSync: (workspaceId, provider, enabled) => client.patch(`/workspaces/${workspaceId}/sync`, { provider, enabled })
387
+ };
388
+ return {
389
+ workspaceTypeApi,
390
+ workspaceApi,
391
+ memberApi,
392
+ messageApi,
393
+ artefactApi,
394
+ reactionApi,
395
+ agentApi,
396
+ summaryApi,
397
+ webhookApi,
398
+ syncApi
399
+ };
400
+ }
401
+
402
+ // src/store/useWorkspaceListStore.ts
403
+ import { create } from "zustand";
404
+ var PAGE_SIZE = 12;
405
+ function createWorkspaceListStore(apis) {
406
+ return create()((set, get) => ({
407
+ workspaces: [],
408
+ total: 0,
409
+ membersMap: {},
410
+ types: [],
411
+ tab: "group",
412
+ page: 1,
413
+ pageSize: PAGE_SIZE,
414
+ totalPages: 0,
415
+ filters: { search: "", status: "ACTIVE", type: "all" },
416
+ initialLoading: true,
417
+ fetching: false,
418
+ hydrated: false,
419
+ setTab: (tab) => {
420
+ const prev = get().tab;
421
+ if (prev === tab) return;
422
+ set({
423
+ tab,
424
+ page: 1,
425
+ workspaces: [],
426
+ total: 0,
427
+ totalPages: 0,
428
+ membersMap: {}
429
+ });
430
+ get().fetchWorkspaces();
431
+ },
432
+ setFilters: (filters) => {
433
+ set({ filters, page: 1 });
434
+ },
435
+ setPage: (page) => {
436
+ set({ page });
437
+ },
438
+ fetchTypes: async () => {
439
+ try {
440
+ const types = await apis.workspaceTypeApi.list();
441
+ set({ types });
442
+ } catch (err) {
443
+ console.error("Failed to load workspace types:", err);
444
+ }
445
+ },
446
+ fetchWorkspaces: async () => {
447
+ const { filters, page, pageSize, hydrated, tab } = get();
448
+ if (!hydrated) set({ initialLoading: true });
449
+ set({ fetching: true });
450
+ try {
451
+ const skip = (page - 1) * pageSize;
452
+ const result = await apis.workspaceApi.list({
453
+ ...filters,
454
+ isPrivate: tab === "private",
455
+ limit: pageSize,
456
+ skip
457
+ });
458
+ const wsList = result.data;
459
+ const totalPages = Math.ceil(result.total / pageSize);
460
+ const map = {};
461
+ await Promise.all(
462
+ wsList.map(async (ws) => {
463
+ try {
464
+ map[ws._id] = await apis.memberApi.list(ws._id);
465
+ } catch {
466
+ map[ws._id] = [];
467
+ }
468
+ })
469
+ );
470
+ set({
471
+ workspaces: wsList,
472
+ total: result.total,
473
+ totalPages,
474
+ membersMap: map,
475
+ hydrated: true,
476
+ initialLoading: false,
477
+ fetching: false
478
+ });
479
+ } catch (err) {
480
+ console.error("Failed to load workspaces:", err);
481
+ set({ initialLoading: false, fetching: false });
482
+ }
483
+ },
484
+ prependWorkspace: (ws, members = []) => {
485
+ set((state) => {
486
+ if (state.workspaces.some((w) => w._id === ws._id)) return state;
487
+ return {
488
+ workspaces: [ws, ...state.workspaces],
489
+ total: state.total + 1,
490
+ totalPages: Math.ceil((state.total + 1) / state.pageSize),
491
+ membersMap: { ...state.membersMap, [ws._id]: members }
492
+ };
493
+ });
494
+ },
495
+ removeWorkspace: (workspaceId) => {
496
+ set((state) => {
497
+ const next = { ...state.membersMap };
498
+ delete next[workspaceId];
499
+ const newTotal = Math.max(0, state.total - 1);
500
+ return {
501
+ workspaces: state.workspaces.filter((w) => w._id !== workspaceId),
502
+ total: newTotal,
503
+ totalPages: Math.ceil(newTotal / state.pageSize),
504
+ membersMap: next
505
+ };
506
+ });
507
+ },
508
+ refresh: async () => {
509
+ const { fetchWorkspaces } = get();
510
+ await fetchWorkspaces();
511
+ }
512
+ }));
513
+ }
514
+
515
+ // src/hooks/useWorkspaceApi.ts
516
+ import { useMemo as useMemo2 } from "react";
517
+ function useWorkspaceApi() {
518
+ const { apiClient } = useWorkspaceContext();
519
+ return useMemo2(() => createWorkspaceApis(apiClient), [apiClient]);
520
+ }
521
+
522
+ // src/utils/constants.ts
523
+ var AVAILABLE_AGENTS = [
524
+ { memberId: "james-agent", memberName: "James", memberType: "agent" },
525
+ { memberId: "eclaireur-agent", memberName: "\xC9claireur IA", memberType: "agent" },
526
+ { memberId: "sentinelle-agent", memberName: "Sentinelle", memberType: "agent" },
527
+ { memberId: "archiviste-agent", memberName: "Archiviste", memberType: "agent" },
528
+ { memberId: "stratege-agent", memberName: "Strat\xE8ge", memberType: "agent" }
529
+ ];
530
+ var ROLE_LABELS = {
531
+ owner: "Propri\xE9taire",
532
+ participant: "Participant",
533
+ viewer: "Observateur"
534
+ };
535
+ var STATUS_LABELS = {
536
+ ACTIVE: "Actif",
537
+ CLOSED: "Ferm\xE9",
538
+ ARCHIVED: "Archiv\xE9"
539
+ };
540
+ function getInitials(name) {
541
+ return name.split(/\s+/).map((w) => w[0]).join("").toUpperCase().slice(0, 2);
542
+ }
543
+
544
+ // src/utils/formatExport.ts
545
+ var ROLE_LABELS2 = {
546
+ owner: "Propri\xE9taire",
547
+ participant: "Participant",
548
+ viewer: "Observateur"
549
+ };
550
+ var MEMBER_TYPE_LABELS = {
551
+ user: "Utilisateur",
552
+ agent: "Agent IA"
553
+ };
554
+ function formatDate(iso) {
555
+ return new Date(iso).toLocaleDateString("fr-FR", {
556
+ weekday: "long",
557
+ year: "numeric",
558
+ month: "long",
559
+ day: "numeric"
560
+ });
561
+ }
562
+ function formatDateTime(iso) {
563
+ return new Date(iso).toLocaleString("fr-FR", {
564
+ year: "numeric",
565
+ month: "2-digit",
566
+ day: "2-digit",
567
+ hour: "2-digit",
568
+ minute: "2-digit"
569
+ });
570
+ }
571
+ function getTypeName(workspace) {
572
+ if (!workspace.type) return "\u2014";
573
+ if (typeof workspace.type === "object") return workspace.type.name;
574
+ return workspace.type;
575
+ }
576
+ function fixMojibake(text) {
577
+ return text.replace(/é/g, "\xE9").replace(/è/g, "\xE8").replace(/à /g, "\xE0").replace(/ç/g, "\xE7").replace(/ô/g, "\xF4").replace(/î/g, "\xEE").replace(/â/g, "\xE2").replace(/ë/g, "\xEB").replace(/ü/g, "\xFC");
578
+ }
579
+ function formatWorkspaceExport(data) {
580
+ const { workspace, members, messages } = data;
581
+ const lines = [];
582
+ lines.push(`# \u{1F4CB} Export \u2014 ${workspace.name}`);
583
+ lines.push("");
584
+ lines.push(`> Export g\xE9n\xE9r\xE9 le **${formatDateTime((/* @__PURE__ */ new Date()).toISOString())}**`);
585
+ lines.push("");
586
+ lines.push("---");
587
+ lines.push("");
588
+ lines.push("## \u{1F4CC} Contexte");
589
+ lines.push("");
590
+ lines.push(`| Champ | Valeur |`);
591
+ lines.push(`| ----- | ------ |`);
592
+ lines.push(`| **Nom** | ${workspace.name} |`);
593
+ lines.push(`| **Type** | ${getTypeName(workspace)} |`);
594
+ lines.push(`| **Scope** | ${workspace.scope || "\u2014"} |`);
595
+ lines.push(`| **Statut** | ${workspace.status} |`);
596
+ lines.push(`| **Cr\xE9\xE9 le** | ${formatDate(workspace.createdAt)} |`);
597
+ lines.push(`| **Mis \xE0 jour le** | ${formatDate(workspace.updatedAt)} |`);
598
+ const displayEntities = workspace.entitiesWithSecrets ?? [];
599
+ if (displayEntities.length > 0) {
600
+ lines.push("");
601
+ lines.push("### Entit\xE9s");
602
+ lines.push("");
603
+ lines.push("| Cl\xE9 | Valeur | Secret |");
604
+ lines.push("| --- | ------ | ------ |");
605
+ for (const ent of displayEntities) {
606
+ lines.push(
607
+ `| ${ent.key} | ${ent.isSecret ? "\u2022\u2022\u2022\u2022\u2022\u2022" : ent.value} | ${ent.isSecret ? "\u{1F512} Oui" : "Non"} |`
608
+ );
609
+ }
610
+ }
611
+ lines.push("");
612
+ lines.push("---");
613
+ lines.push("");
614
+ lines.push(`## \u{1F465} Membres (${members.length})`);
615
+ lines.push("");
616
+ lines.push("| Nom | Type | R\xF4le | Rejoint le |");
617
+ lines.push("| --- | ---- | ---- | ---------- |");
618
+ for (const m of members) {
619
+ lines.push(
620
+ `| ${m.memberName} | ${MEMBER_TYPE_LABELS[m.memberType] || m.memberType} | ${ROLE_LABELS2[m.role] || m.role} | ${m.joinedAt || m.createdAt ? formatDate(m.joinedAt || m.createdAt) : "\u2014"} |`
621
+ );
622
+ }
623
+ lines.push("");
624
+ lines.push("---");
625
+ lines.push("");
626
+ lines.push(`## \u{1F4AC} Discussion (${messages.length} messages)`);
627
+ lines.push("");
628
+ if (messages.length === 0) {
629
+ lines.push("_Aucun message dans ce workspace._");
630
+ } else {
631
+ let currentDay = "";
632
+ for (const msg of messages) {
633
+ const day = formatDate(msg.createdAt);
634
+ if (day !== currentDay) {
635
+ currentDay = day;
636
+ lines.push(`### \u{1F4C5} ${day}`);
637
+ lines.push("");
638
+ }
639
+ const timeStr = new Date(msg.createdAt).toLocaleTimeString("fr-FR", {
640
+ hour: "2-digit",
641
+ minute: "2-digit"
642
+ });
643
+ const senderIcon = msg.senderType === "agent" ? "\u{1F916}" : "\u{1F464}";
644
+ lines.push(`**${senderIcon} ${msg.senderName}** \u2014 _${timeStr}_`);
645
+ lines.push("");
646
+ lines.push(msg.content);
647
+ lines.push("");
648
+ if (msg.artefacts && msg.artefacts.length > 0) {
649
+ lines.push("> \u{1F4CE} Pi\xE8ces jointes :");
650
+ for (const art of msg.artefacts) {
651
+ lines.push(`> - \`${fixMojibake(art.file.originalName)}\` (${art.type})`);
652
+ }
653
+ lines.push("");
654
+ }
655
+ lines.push("---");
656
+ lines.push("");
657
+ }
658
+ }
659
+ lines.push("");
660
+ lines.push(`_Fin de l'export \u2014 ${workspace.name}_`);
661
+ return lines.join("\n");
662
+ }
663
+
664
+ // src/components/ui/Modal.tsx
665
+ import { useEffect, useId, useState } from "react";
666
+ import { createPortal } from "react-dom";
667
+ import { X } from "lucide-react";
668
+
669
+ // src/components/ui/cn.ts
670
+ import { clsx } from "clsx";
671
+ import { twMerge } from "tailwind-merge";
672
+ function cn(...inputs) {
673
+ return twMerge(clsx(inputs));
674
+ }
675
+
676
+ // src/components/ui/Modal.tsx
677
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
678
+ var MODAL_SIZE_CLASS = {
679
+ sm: "w-full min-w-0 max-w-sm",
680
+ md: "w-full min-w-0 max-w-md",
681
+ lg: "w-full min-w-0 max-w-lg",
682
+ xl: "w-full min-w-0 max-w-xl",
683
+ "2xl": "w-full min-w-0 max-w-2xl",
684
+ "3xl": "w-full min-w-0 max-w-3xl",
685
+ "4xl": "w-full min-w-0 max-w-4xl",
686
+ "5xl": "w-full min-w-0 max-w-5xl",
687
+ "6xl": "w-full min-w-0 max-w-6xl",
688
+ "7xl": "w-full min-w-0 max-w-7xl",
689
+ full: "w-full min-w-0 max-w-[calc(100vw-2rem)] sm:max-w-[min(96vw,90rem)]"
690
+ };
691
+ function Modal({
692
+ open,
693
+ onClose,
694
+ children,
695
+ zIndexClass = "z-[100]",
696
+ backdropClassName = "bg-black/40 backdrop-blur-sm",
697
+ size,
698
+ panelClassName,
699
+ className,
700
+ closeOnBackdropClick = false,
701
+ closeOnEscape = true,
702
+ ariaLabelledBy: ariaLabelledByProp,
703
+ ariaDescribedBy: ariaDescribedByProp,
704
+ title,
705
+ description,
706
+ titleIcon,
707
+ headingId,
708
+ modalHeaderVariant = "strip",
709
+ hideCloseButton = false,
710
+ headerTrailing,
711
+ closeButtonAriaLabel = "Fermer",
712
+ closeButtonDisabled = false,
713
+ closeButtonClassName,
714
+ closeIconSize = 20,
715
+ headerClassName,
716
+ titleClassName,
717
+ descriptionClassName = "text-text-secondary mt-1 text-sm"
718
+ }) {
719
+ const [mounted, setMounted] = useState(false);
720
+ const autoHeadingId = useId();
721
+ const autoDescIdByModal = useId();
722
+ const hasHeader = title != null;
723
+ const resolvedHeadingId = headingId ?? ariaLabelledByProp ?? autoHeadingId;
724
+ const assignAutoDescriptionId = hasHeader && description != null && ariaDescribedByProp === void 0 && (typeof description === "string" || typeof description === "number");
725
+ const resolvedAriaDescribedBy = assignAutoDescriptionId ? autoDescIdByModal : ariaDescribedByProp;
726
+ const resolvedAriaLabelledBy = hasHeader ? ariaLabelledByProp ?? resolvedHeadingId : ariaLabelledByProp;
727
+ useEffect(() => {
728
+ setMounted(true);
729
+ }, []);
730
+ useEffect(() => {
731
+ if (!open) return;
732
+ const prevOverflow = document.body.style.overflow;
733
+ document.body.style.overflow = "hidden";
734
+ return () => {
735
+ document.body.style.overflow = prevOverflow;
736
+ };
737
+ }, [open]);
738
+ useEffect(() => {
739
+ if (!open || !closeOnEscape) return;
740
+ const onKeyDown = (e) => {
741
+ if (e.key === "Escape") onClose();
742
+ };
743
+ window.addEventListener("keydown", onKeyDown);
744
+ return () => window.removeEventListener("keydown", onKeyDown);
745
+ }, [open, closeOnEscape, onClose]);
746
+ const defaultCloseClasses = closeButtonClassName ?? "text-text-secondary hover:text-text rounded p-1 transition-colors";
747
+ const trailing = headerTrailing ?? (!hideCloseButton ? /* @__PURE__ */ jsx2(
748
+ "button",
749
+ {
750
+ type: "button",
751
+ disabled: closeButtonDisabled,
752
+ onClick: onClose,
753
+ className: cn(defaultCloseClasses, closeButtonDisabled && "opacity-40"),
754
+ "aria-label": closeButtonAriaLabel,
755
+ children: /* @__PURE__ */ jsx2(X, { size: closeIconSize })
756
+ }
757
+ ) : null);
758
+ const descriptionSlot = description == null ? null : typeof description === "string" || typeof description === "number" ? /* @__PURE__ */ jsx2(
759
+ "p",
760
+ {
761
+ id: assignAutoDescriptionId ? autoDescIdByModal : void 0,
762
+ className: descriptionClassName,
763
+ children: description
764
+ }
765
+ ) : description;
766
+ const headerShellClass = modalHeaderVariant === "strip" ? "border-border flex shrink-0 items-start justify-between gap-3 border-b px-6 py-4" : "mb-4 flex shrink-0 items-start justify-between gap-3";
767
+ const modalHeaderMarkup = hasHeader ? /* @__PURE__ */ jsxs("div", { className: cn(headerShellClass, headerClassName), children: [
768
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-start gap-3", children: [
769
+ titleIcon != null ? /* @__PURE__ */ jsx2("div", { className: "mt-0.5 shrink-0", children: titleIcon }) : null,
770
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
771
+ /* @__PURE__ */ jsx2(
772
+ "h2",
773
+ {
774
+ id: resolvedHeadingId,
775
+ className: cn("text-text text-lg font-semibold", titleClassName),
776
+ children: title
777
+ }
778
+ ),
779
+ descriptionSlot
780
+ ] })
781
+ ] }),
782
+ trailing ? /* @__PURE__ */ jsx2("div", { className: "flex shrink-0 items-start", children: trailing }) : null
783
+ ] }) : null;
784
+ if (!mounted || !open) return null;
785
+ return createPortal(
786
+ /* @__PURE__ */ jsx2(
787
+ "div",
788
+ {
789
+ className: cn(
790
+ "fixed inset-0 flex items-center justify-center p-4",
791
+ zIndexClass,
792
+ backdropClassName,
793
+ className
794
+ ),
795
+ onClick: closeOnBackdropClick ? onClose : void 0,
796
+ "aria-hidden": false,
797
+ children: /* @__PURE__ */ jsxs(
798
+ "div",
799
+ {
800
+ role: "dialog",
801
+ "aria-modal": "true",
802
+ "aria-labelledby": resolvedAriaLabelledBy,
803
+ "aria-describedby": resolvedAriaDescribedBy,
804
+ className: cn(size ? MODAL_SIZE_CLASS[size] : void 0, panelClassName),
805
+ onClick: (e) => e.stopPropagation(),
806
+ children: [
807
+ modalHeaderMarkup,
808
+ children
809
+ ]
810
+ }
811
+ )
812
+ }
813
+ ),
814
+ document.body
815
+ );
816
+ }
817
+
818
+ // src/components/MemberAvatar.tsx
819
+ import { Brain } from "lucide-react";
820
+ import { jsx as jsx3 } from "react/jsx-runtime";
821
+ var SIZES = {
822
+ xs: { container: "h-6 w-6", text: "text-[9px]", icon: 10 },
823
+ sm: { container: "h-7 w-7", text: "text-[10px]", icon: 12 },
824
+ md: { container: "h-9 w-9", text: "text-xs", icon: 16 },
825
+ lg: { container: "h-10 w-10", text: "text-sm", icon: 18 }
826
+ };
827
+ var MemberAvatar = ({
828
+ memberName,
829
+ memberType,
830
+ avatarUrl,
831
+ size = "md",
832
+ className,
833
+ variant = "primary"
834
+ }) => {
835
+ const s = SIZES[size];
836
+ if (memberType === "agent") {
837
+ return /* @__PURE__ */ jsx3("span", { className: cn("flex flex-shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-violet-100 to-indigo-100 ring-1 ring-indigo-200/60", s.container, className), children: /* @__PURE__ */ jsx3(Brain, { size: s.icon, className: "text-indigo-600" }) });
838
+ }
839
+ if (avatarUrl) {
840
+ return /* @__PURE__ */ jsx3("img", { src: avatarUrl, alt: memberName, className: cn("flex-shrink-0 rounded-full object-cover ring-2 ring-white", s.container, className) });
841
+ }
842
+ return /* @__PURE__ */ jsx3("span", { className: cn("flex flex-shrink-0 items-center justify-center rounded-full font-semibold", s.container, s.text, variant === "currentUser" ? "bg-primary text-white shadow-md" : variant === "owner" ? "bg-warning-light text-warning" : "bg-primary-light text-primary", className), children: getInitials(memberName) });
843
+ };
844
+
845
+ // src/components/MembersPanel.tsx
846
+ import { useState as useState2, useEffect as useEffect2, useMemo as useMemo3 } from "react";
847
+ import { UserPlus, Crown, Eye, Users, X as X2, Search, Plus, Brain as Brain2, Loader2, Check } from "lucide-react";
848
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
849
+ var roleIcon = (role) => {
850
+ switch (role) {
851
+ case "owner":
852
+ return /* @__PURE__ */ jsx4(Crown, { size: 11, className: "text-warning" });
853
+ case "viewer":
854
+ return /* @__PURE__ */ jsx4(Eye, { size: 11, className: "text-text-muted" });
855
+ default:
856
+ return /* @__PURE__ */ jsx4(Users, { size: 11, className: "text-primary" });
857
+ }
858
+ };
859
+ var MembersPanel = ({
860
+ members,
861
+ avatarUsers,
862
+ onAddMember,
863
+ onRemoveMember,
864
+ onUpdateRole,
865
+ readonly
866
+ }) => {
867
+ const { config } = useWorkspaceContext();
868
+ const apis = useWorkspaceApi();
869
+ const [showUserSearch, setShowUserSearch] = useState2(false);
870
+ const [search, setSearch] = useState2("");
871
+ const [searchResults, setSearchResults] = useState2([]);
872
+ const [searchingUsers, setSearchingUsers] = useState2(false);
873
+ const [allAgents, setAllAgents] = useState2([]);
874
+ useEffect2(() => {
875
+ apis.agentApi.list().then(setAllAgents).catch(() => setAllAgents([]));
876
+ }, []);
877
+ useEffect2(() => {
878
+ if (search.length < 2 || !showUserSearch || !config.searchUsers) {
879
+ setSearchResults([]);
880
+ return;
881
+ }
882
+ let cancelled = false;
883
+ setSearchingUsers(true);
884
+ config.searchUsers(search).then((results) => {
885
+ if (!cancelled) setSearchResults(results);
886
+ }).catch(() => {
887
+ if (!cancelled) setSearchResults([]);
888
+ }).finally(() => {
889
+ if (!cancelled) setSearchingUsers(false);
890
+ });
891
+ return () => {
892
+ cancelled = true;
893
+ };
894
+ }, [search, showUserSearch, config.searchUsers]);
895
+ const filteredUsers = useMemo3(() => {
896
+ return searchResults.filter(
897
+ (u) => u.email !== config.currentUserId && !members.some((m) => m.memberId === u.email)
898
+ );
899
+ }, [searchResults, members, config.currentUserId]);
900
+ const userMembers = members.filter((m) => m.memberType === "user");
901
+ const agentMembers = members.filter((m) => m.memberType === "agent");
902
+ const agentMemberIds = useMemo3(() => new Set(agentMembers.map((m) => m.memberId)), [agentMembers]);
903
+ const agentMemberIdMap = useMemo3(() => new Map(agentMembers.map((m) => [m.memberId, m._id])), [agentMembers]);
904
+ const handleAddUser = (user) => {
905
+ onAddMember?.({ memberId: user.email, memberName: user.displayName, memberType: "user", role: "participant" });
906
+ setSearch("");
907
+ setShowUserSearch(false);
908
+ };
909
+ const handleAddAgent = (agent) => {
910
+ onAddMember?.({ memberId: agent.agentId, memberName: agent.agentName, memberType: "agent", role: "participant" });
911
+ };
912
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-4", children: [
913
+ /* @__PURE__ */ jsxs2("div", { children: [
914
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
915
+ /* @__PURE__ */ jsxs2("span", { className: "text-text-muted flex items-center gap-1.5 text-xs font-semibold tracking-wider uppercase", children: [
916
+ /* @__PURE__ */ jsx4(Users, { size: 14, className: "text-primary" }),
917
+ " Utilisateurs"
918
+ ] }),
919
+ /* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-1", children: [
920
+ /* @__PURE__ */ jsx4("span", { className: "bg-primary-light text-primary rounded-full px-2 py-0.5 text-[10px] font-bold", children: userMembers.length }),
921
+ !readonly && /* @__PURE__ */ jsxs2("button", { className: "text-primary hover:bg-primary-light flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors", onClick: () => setShowUserSearch(!showUserSearch), children: [
922
+ /* @__PURE__ */ jsx4(UserPlus, { size: 12 }),
923
+ " Inviter"
924
+ ] })
925
+ ] })
926
+ ] }),
927
+ showUserSearch && /* @__PURE__ */ jsxs2(Fragment, { children: [
928
+ /* @__PURE__ */ jsxs2("div", { className: "relative mt-2", children: [
929
+ /* @__PURE__ */ jsx4(Search, { size: 12, className: "text-text-muted absolute top-1/2 left-2 -translate-y-1/2" }),
930
+ /* @__PURE__ */ jsx4("input", { className: "border-border bg-surface text-text focus:border-primary w-full rounded-lg border py-1.5 pr-2 pl-7 text-xs focus:outline-none", placeholder: "Rechercher par nom ou email...", value: search, onChange: (e) => setSearch(e.target.value), autoFocus: true })
931
+ ] }),
932
+ search.length >= 2 && searchingUsers && /* @__PURE__ */ jsx4("div", { className: "flex items-center justify-center py-3", children: /* @__PURE__ */ jsx4(Loader2, { size: 14, className: "text-primary animate-spin" }) }),
933
+ filteredUsers.length > 0 && !searchingUsers && /* @__PURE__ */ jsx4("div", { className: "bg-surface border-border mt-1 flex flex-col rounded-lg border shadow-sm", children: filteredUsers.map((u) => /* @__PURE__ */ jsxs2("div", { className: "hover:bg-primary-light flex cursor-pointer items-center gap-2 px-3 py-2 text-xs transition-colors first:rounded-t-lg last:rounded-b-lg", onClick: () => handleAddUser(u), children: [
934
+ /* @__PURE__ */ jsx4("span", { className: "bg-primary-light text-primary flex h-6 w-6 items-center justify-center rounded-full text-[10px] font-semibold", children: getInitials(u.displayName) }),
935
+ /* @__PURE__ */ jsx4("span", { className: "text-text", children: u.displayName }),
936
+ /* @__PURE__ */ jsx4(Plus, { size: 12, className: "text-primary ml-auto" })
937
+ ] }, u.id)) })
938
+ ] }),
939
+ /* @__PURE__ */ jsx4("div", { className: "mt-2 flex flex-col gap-1", children: userMembers.map((m) => /* @__PURE__ */ jsxs2("div", { className: "bg-bg group flex items-center gap-2 rounded-lg p-2", children: [
940
+ /* @__PURE__ */ jsx4(MemberAvatar, { memberName: m.memberName, memberType: m.memberType, avatarUrl: avatarUsers?.find((u) => u.email.toLowerCase() === m.memberId.toLowerCase())?.avatarUrl, size: "sm", variant: m.role === "owner" ? "owner" : "primary" }),
941
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
942
+ /* @__PURE__ */ jsx4("div", { className: "text-text truncate text-xs font-medium", children: m.memberName }),
943
+ /* @__PURE__ */ jsxs2("div", { className: "text-text-muted flex items-center gap-1 text-[10px]", children: [
944
+ roleIcon(m.role),
945
+ " ",
946
+ ROLE_LABELS[m.role] ?? m.role
947
+ ] })
948
+ ] }),
949
+ !(readonly || m.role === "owner" && members.filter((x) => x.role === "owner").length === 1) && /* @__PURE__ */ jsxs2("select", { className: "border-border bg-surface text-text-muted rounded border px-1 py-0.5 text-[10px] opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100", value: m.role, onChange: (e) => {
950
+ e.stopPropagation();
951
+ onUpdateRole?.(m._id, e.target.value);
952
+ }, disabled: readonly || m.role === "owner" && members.filter((x) => x.role === "owner").length === 1, children: [
953
+ /* @__PURE__ */ jsx4("option", { value: "owner", children: "Propri\xE9taire" }),
954
+ /* @__PURE__ */ jsx4("option", { value: "participant", children: "Participant" }),
955
+ /* @__PURE__ */ jsx4("option", { value: "viewer", children: "Observateur" })
956
+ ] }),
957
+ m.role !== "owner" && !readonly && /* @__PURE__ */ jsx4("button", { className: "text-text-muted hover:text-danger rounded p-0.5 opacity-0 transition-all group-hover:opacity-100", onClick: (e) => {
958
+ e.stopPropagation();
959
+ onRemoveMember?.(m._id);
960
+ }, children: /* @__PURE__ */ jsx4(X2, { size: 14 }) })
961
+ ] }, m._id)) })
962
+ ] }),
963
+ /* @__PURE__ */ jsxs2("div", { children: [
964
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
965
+ /* @__PURE__ */ jsxs2("span", { className: "text-text-muted flex items-center gap-1.5 text-xs font-semibold tracking-wider uppercase", children: [
966
+ /* @__PURE__ */ jsx4(Brain2, { size: 14, className: "text-primary" }),
967
+ " Agents IA"
968
+ ] }),
969
+ /* @__PURE__ */ jsxs2("span", { className: "bg-primary-light text-primary rounded-full px-2 py-0.5 text-[10px] font-bold", children: [
970
+ agentMembers.length,
971
+ "/",
972
+ allAgents.length
973
+ ] })
974
+ ] }),
975
+ /* @__PURE__ */ jsx4("div", { className: "mt-2 flex flex-col gap-1", children: allAgents.map((agent) => {
976
+ const isMember = agentMemberIds.has(agent.agentId);
977
+ const memberId = agentMemberIdMap.get(agent.agentId);
978
+ return /* @__PURE__ */ jsxs2("div", { className: "bg-bg group flex items-center gap-2 rounded-lg p-2", children: [
979
+ /* @__PURE__ */ jsx4("span", { className: "bg-surface border-border flex h-7 w-7 items-center justify-center rounded-full border", children: /* @__PURE__ */ jsx4(Brain2, { size: 14, className: "text-primary" }) }),
980
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
981
+ /* @__PURE__ */ jsx4("div", { className: "text-text truncate text-xs font-medium", children: agent.agentName }),
982
+ /* @__PURE__ */ jsx4("div", { className: "text-text-muted text-[10px]", children: "Agent IA" })
983
+ ] }),
984
+ !readonly && (isMember ? /* @__PURE__ */ jsxs2("button", { className: "relative flex h-6 w-6 items-center justify-center rounded-full transition-colors", onClick: (e) => {
985
+ e.stopPropagation();
986
+ if (memberId) onRemoveMember?.(memberId);
987
+ }, children: [
988
+ /* @__PURE__ */ jsx4(Check, { size: 14, className: "text-success absolute transition-opacity group-hover:opacity-0" }),
989
+ /* @__PURE__ */ jsx4(X2, { size: 14, className: "text-danger absolute opacity-0 transition-opacity group-hover:opacity-100" })
990
+ ] }) : /* @__PURE__ */ jsx4("button", { className: "text-text-muted hover:text-primary hover:bg-primary-light flex h-6 w-6 items-center justify-center rounded-full transition-colors", onClick: (e) => {
991
+ e.stopPropagation();
992
+ handleAddAgent(agent);
993
+ }, children: /* @__PURE__ */ jsx4(Plus, { size: 14 }) }))
994
+ ] }, agent.agentId);
995
+ }) })
996
+ ] })
997
+ ] });
998
+ };
999
+
1000
+ // src/components/CreateWorkspaceModal.tsx
1001
+ import { useState as useState3, useEffect as useEffect3, useMemo as useMemo4, useId as useId2, useRef } from "react";
1002
+ import { X as X3, ArrowRight, ArrowLeft, Plus as Plus2, Trash2, Check as Check2, Users as Users2, Brain as Brain3, Loader2 as Loader22, Lock, Unlock, Slack } from "lucide-react";
1003
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1004
+ var STEP_TITLES = ["Type de workspace", "Contexte", "Membres", "Confirmation"];
1005
+ var CreateWorkspaceModal = ({ open, onClose, onCreated }) => {
1006
+ const titleId = useId2();
1007
+ const stepDescriptionId = useId2();
1008
+ const { config } = useWorkspaceContext();
1009
+ const apis = useWorkspaceApi();
1010
+ const [step, setStep] = useState3(0);
1011
+ const [loading, setLoading] = useState3(false);
1012
+ const [error, setError] = useState3(null);
1013
+ const [types, setTypes] = useState3([]);
1014
+ const [typesLoading, setTypesLoading] = useState3(false);
1015
+ const [selectedTypeId, setSelectedTypeId] = useState3(null);
1016
+ const [name, setName] = useState3("");
1017
+ const [scope, setScope] = useState3("");
1018
+ const [entities, setEntities] = useState3([]);
1019
+ const [syncWithSlack, setSyncWithSlack] = useState3(false);
1020
+ const [slackAvailable, setSlackAvailable] = useState3(false);
1021
+ const [memberSearch, setMemberSearch] = useState3("");
1022
+ const [members, setMembers] = useState3([]);
1023
+ const [agents, setAgents] = useState3([]);
1024
+ const [searchResults, setSearchResults] = useState3([]);
1025
+ const [searchingUsers, setSearchingUsers] = useState3(false);
1026
+ const [avatarMap, setAvatarMap] = useState3({});
1027
+ const [agentSearch, setAgentSearch] = useState3("");
1028
+ const [showAgentDropdown, setShowAgentDropdown] = useState3(false);
1029
+ const [agentSearchFocused, setAgentSearchFocused] = useState3(false);
1030
+ const agentSearchInputRef = useRef(null);
1031
+ const invitedUsers = useMemo4(() => members.filter((m) => m.memberType === "user"), [members]);
1032
+ const invitedAgents = useMemo4(() => members.filter((m) => m.memberType === "agent"), [members]);
1033
+ const unselectedAgents = useMemo4(() => agents.filter((a) => !members.some((m) => m.memberId === a.agentId) && (agentSearch.trim() === "" || a.agentName.toLowerCase().includes(agentSearch.toLowerCase()))), [agents, members, agentSearch]);
1034
+ useEffect3(() => {
1035
+ if (!config.resolveAvatars || invitedUsers.length === 0) return;
1036
+ config.resolveAvatars(invitedUsers.map((m) => m.memberId)).then(setAvatarMap).catch(() => {
1037
+ });
1038
+ }, [invitedUsers.length]);
1039
+ useEffect3(() => {
1040
+ if (memberSearch.length < 2 || !config.searchUsers) {
1041
+ setSearchResults([]);
1042
+ return;
1043
+ }
1044
+ let cancelled = false;
1045
+ setSearchingUsers(true);
1046
+ config.searchUsers(memberSearch).then((r) => {
1047
+ if (!cancelled) setSearchResults(r);
1048
+ }).catch(() => {
1049
+ if (!cancelled) setSearchResults([]);
1050
+ }).finally(() => {
1051
+ if (!cancelled) setSearchingUsers(false);
1052
+ });
1053
+ return () => {
1054
+ cancelled = true;
1055
+ };
1056
+ }, [memberSearch]);
1057
+ const filteredUsers = useMemo4(() => searchResults.filter((u) => u.email !== config.currentUserId && !members.some((m) => m.memberId === u.email)), [searchResults, members, config.currentUserId]);
1058
+ useEffect3(() => {
1059
+ if (!open) return;
1060
+ setTypesLoading(true);
1061
+ Promise.all([apis.workspaceTypeApi.list(), apis.agentApi.list()]).then(([wt, ag]) => {
1062
+ setTypes(wt);
1063
+ setAgents(ag);
1064
+ }).catch(() => {
1065
+ setTypes([]);
1066
+ setAgents([]);
1067
+ }).finally(() => setTypesLoading(false));
1068
+ apis.syncApi.getAvailableProviders().then((providers) => setSlackAvailable(providers.some((p) => p.id === "slack" && p.enabled))).catch(() => setSlackAvailable(false));
1069
+ }, [open]);
1070
+ useEffect3(() => {
1071
+ if (!selectedTypeId) return;
1072
+ const t = types.find((tp) => tp._id === selectedTypeId);
1073
+ if (t?.entitiesWithSecrets && t.entitiesWithSecrets.length > 0) {
1074
+ setEntities(t.entitiesWithSecrets.map((e) => ({ key: e.key, value: e.value, isSecret: e.isSecret })));
1075
+ }
1076
+ }, [selectedTypeId, types]);
1077
+ useEffect3(() => {
1078
+ if (!open) {
1079
+ setStep(0);
1080
+ setSelectedTypeId(null);
1081
+ setName("");
1082
+ setScope("");
1083
+ setEntities([]);
1084
+ setSyncWithSlack(false);
1085
+ setMembers([]);
1086
+ setMemberSearch("");
1087
+ setError(null);
1088
+ }
1089
+ }, [open]);
1090
+ const selectedType = types.find((t) => t._id === selectedTypeId);
1091
+ const canAdvance = () => {
1092
+ switch (step) {
1093
+ case 0:
1094
+ return !!selectedTypeId;
1095
+ case 1:
1096
+ return name.trim().length > 0;
1097
+ default:
1098
+ return true;
1099
+ }
1100
+ };
1101
+ const addMember = (memberId, memberName, memberType) => {
1102
+ if (members.some((m) => m.memberId === memberId)) return;
1103
+ setMembers((prev) => [...prev, { memberId, memberName, memberType, role: "participant" }]);
1104
+ setMemberSearch("");
1105
+ };
1106
+ const removeMember = (memberId) => setMembers((prev) => prev.filter((m) => m.memberId !== memberId));
1107
+ const handleCreate = async () => {
1108
+ setLoading(true);
1109
+ setError(null);
1110
+ try {
1111
+ const entitiesWithSecrets = entities.filter((e) => e.key.trim()).map((e) => ({ key: e.key.trim(), value: e.value, isSecret: e.isSecret }));
1112
+ const payload = {
1113
+ name: name.trim(),
1114
+ type: selectedTypeId || void 0,
1115
+ scope: scope.trim() || void 0,
1116
+ entitiesWithSecrets: entitiesWithSecrets.length > 0 ? entitiesWithSecrets : void 0,
1117
+ members: members.length > 0 ? members.map((m) => ({ memberId: m.memberId, memberName: m.memberName, memberType: m.memberType, role: m.role })) : void 0,
1118
+ syncWithSlack: syncWithSlack || void 0
1119
+ };
1120
+ const ws = await apis.workspaceApi.create(payload);
1121
+ onCreated?.(ws);
1122
+ onClose();
1123
+ } catch (err) {
1124
+ setError(err.message);
1125
+ } finally {
1126
+ setLoading(false);
1127
+ }
1128
+ };
1129
+ return /* @__PURE__ */ jsxs3(Modal, { open, onClose, zIndexClass: "z-50", size: "md", closeOnBackdropClick: false, backdropClassName: "bg-black/40", panelClassName: "bg-surface border-border flex h-[600px] max-h-[min(90dvh,100vh)] flex-col overflow-hidden rounded-2xl border shadow-xl", headingId: titleId, ariaLabelledBy: titleId, ariaDescribedBy: stepDescriptionId, title: "Nouveau Workspace", description: /* @__PURE__ */ jsxs3("p", { id: stepDescriptionId, className: "text-text-muted text-xs", children: [
1130
+ "\xC9tape ",
1131
+ step + 1,
1132
+ "/4 \u2014 ",
1133
+ STEP_TITLES[step]
1134
+ ] }), headerClassName: "items-center", closeButtonClassName: "text-text-muted hover:text-text transition-colors", children: [
1135
+ /* @__PURE__ */ jsx5("div", { className: "flex shrink-0 gap-1 px-6 pt-4", children: [0, 1, 2, 3].map((s) => /* @__PURE__ */ jsx5("div", { className: cn("h-1 flex-1 rounded-full transition-colors", s <= step ? "bg-primary" : "bg-border-light") }, s)) }),
1136
+ /* @__PURE__ */ jsxs3("div", { className: "min-h-0 flex-1 overflow-y-auto px-6 py-4", children: [
1137
+ step === 0 && (typesLoading ? /* @__PURE__ */ jsx5("div", { className: "flex h-[220px] items-center justify-center", children: /* @__PURE__ */ jsx5(Loader22, { size: 20, className: "text-primary animate-spin" }) }) : types.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "text-text-muted py-8 text-center text-sm", children: "Aucun type disponible" }) : /* @__PURE__ */ jsx5("div", { className: "grid grid-cols-2 gap-2", children: types.map((t) => /* @__PURE__ */ jsx5("button", { className: cn("flex flex-col items-start gap-1 rounded-xl border p-3 text-left transition-all", selectedTypeId === t._id ? "border-primary bg-primary-light shadow-sm" : "border-border hover:border-primary/50 hover:bg-bg"), onClick: () => setSelectedTypeId(t._id), children: /* @__PURE__ */ jsx5("span", { className: "text-text text-sm font-medium", children: t.name }) }, t._id)) })),
1138
+ step === 1 && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-4", children: [
1139
+ /* @__PURE__ */ jsxs3("div", { children: [
1140
+ /* @__PURE__ */ jsx5("label", { className: "text-text-muted mb-1 block text-xs font-medium", children: "Nom du workspace" }),
1141
+ /* @__PURE__ */ jsx5("input", { className: "border-border bg-bg text-text focus:border-primary w-full rounded-lg border px-3 py-2 text-sm focus:outline-none", placeholder: `Ex: ${selectedType?.name ?? "Workspace"} - ...`, value: name, onChange: (e) => setName(e.target.value), autoFocus: true })
1142
+ ] }),
1143
+ /* @__PURE__ */ jsxs3("div", { children: [
1144
+ /* @__PURE__ */ jsx5("label", { className: "text-text-muted mb-1 block text-xs font-medium", children: "Scope" }),
1145
+ /* @__PURE__ */ jsx5("input", { className: "border-border bg-bg text-text focus:border-primary w-full rounded-lg border px-3 py-2 text-sm focus:outline-none", placeholder: "Ex: production, engineering...", value: scope, onChange: (e) => setScope(e.target.value) })
1146
+ ] }),
1147
+ /* @__PURE__ */ jsxs3("div", { children: [
1148
+ /* @__PURE__ */ jsx5("label", { className: "text-text-muted mb-1 block text-xs font-medium", children: "Entit\xE9s" }),
1149
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
1150
+ entities.map((e, idx) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1151
+ /* @__PURE__ */ jsx5("input", { className: "border-border bg-bg text-text focus:border-primary w-28 rounded border px-2 py-1 text-xs focus:outline-none", value: e.key, onChange: (ev) => {
1152
+ const arr = [...entities];
1153
+ arr[idx].key = ev.target.value;
1154
+ setEntities(arr);
1155
+ }, placeholder: "Cl\xE9" }),
1156
+ /* @__PURE__ */ jsx5("input", { className: "border-border bg-bg text-text focus:border-primary flex-1 rounded border px-2 py-1 text-xs focus:outline-none", value: e.value, onChange: (ev) => {
1157
+ const arr = [...entities];
1158
+ arr[idx].value = ev.target.value;
1159
+ setEntities(arr);
1160
+ }, placeholder: "Valeur" }),
1161
+ /* @__PURE__ */ jsx5("button", { type: "button", className: `rounded p-1 transition-colors ${e.isSecret ? "bg-warning/15 text-warning" : "text-text-muted hover:text-text hover:bg-bg"}`, onClick: () => {
1162
+ const arr = [...entities];
1163
+ arr[idx].isSecret = !arr[idx].isSecret;
1164
+ setEntities(arr);
1165
+ }, children: e.isSecret ? /* @__PURE__ */ jsx5(Lock, { size: 12 }) : /* @__PURE__ */ jsx5(Unlock, { size: 12 }) }),
1166
+ /* @__PURE__ */ jsx5("button", { className: "text-text-muted hover:text-danger", onClick: () => setEntities(entities.filter((_, i) => i !== idx)), children: /* @__PURE__ */ jsx5(Trash2, { size: 14 }) })
1167
+ ] }, idx)),
1168
+ /* @__PURE__ */ jsxs3("button", { className: "text-primary hover:bg-primary-light flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors", onClick: () => setEntities([...entities, { key: "", value: "", isSecret: false }]), children: [
1169
+ /* @__PURE__ */ jsx5(Plus2, { size: 12 }),
1170
+ " Ajouter une entit\xE9"
1171
+ ] })
1172
+ ] })
1173
+ ] }),
1174
+ slackAvailable && /* @__PURE__ */ jsxs3("label", { className: cn("border-border bg-bg hover:border-primary/50 flex cursor-pointer items-start gap-3 rounded-xl border p-3 transition-colors", syncWithSlack && "border-primary bg-primary-light/40"), children: [
1175
+ /* @__PURE__ */ jsx5("input", { type: "checkbox", className: "accent-primary mt-0.5 h-4 w-4 shrink-0 cursor-pointer", checked: syncWithSlack, onChange: (e) => setSyncWithSlack(e.target.checked) }),
1176
+ /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
1177
+ /* @__PURE__ */ jsxs3("span", { className: "text-text flex items-center gap-1.5 text-sm font-medium", children: [
1178
+ /* @__PURE__ */ jsx5(Slack, { size: 14, className: "text-primary" }),
1179
+ " Synchroniser avec Slack"
1180
+ ] }),
1181
+ /* @__PURE__ */ jsx5("span", { className: "text-text-muted text-[11px] leading-snug", children: "Un canal Slack priv\xE9 sera cr\xE9\xE9 et les membres utilisateurs y seront invit\xE9s automatiquement." })
1182
+ ] })
1183
+ ] })
1184
+ ] }),
1185
+ step === 2 && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-5", children: [
1186
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
1187
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
1188
+ /* @__PURE__ */ jsxs3("label", { className: "text-text-muted flex items-center gap-1.5 text-xs font-semibold tracking-wider uppercase", children: [
1189
+ /* @__PURE__ */ jsx5(Users2, { size: 14, className: "text-primary" }),
1190
+ " Membres Utilisateurs"
1191
+ ] }),
1192
+ invitedUsers.length > 0 && /* @__PURE__ */ jsxs3("span", { className: "bg-primary-light text-primary rounded-full px-2 py-0.5 text-[10px] font-bold", children: [
1193
+ invitedUsers.length,
1194
+ " invit\xE9",
1195
+ invitedUsers.length > 1 ? "s" : ""
1196
+ ] })
1197
+ ] }),
1198
+ /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
1199
+ /* @__PURE__ */ jsx5("input", { type: "search", name: "workspace-member-search", autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": "true", className: "border-border bg-bg text-text focus:border-primary focus:ring-primary/10 placeholder:text-text-muted/65 w-full rounded-xl border px-3.5 py-2.5 text-sm transition-all focus:ring-2 focus:outline-none", placeholder: "Rechercher un membre par nom ou identifiant...", value: memberSearch, onChange: (e) => setMemberSearch(e.target.value) }),
1200
+ memberSearch.length >= 2 && searchingUsers && /* @__PURE__ */ jsx5("div", { className: "absolute top-1/2 right-3.5 flex -translate-y-1/2 items-center justify-center py-3", children: /* @__PURE__ */ jsx5(Loader22, { size: 16, className: "text-primary animate-spin" }) }),
1201
+ filteredUsers.length > 0 && memberSearch.length >= 2 && !searchingUsers && /* @__PURE__ */ jsx5("div", { className: "border-border bg-surface divide-border/30 absolute top-full left-0 z-20 mt-1 flex max-h-48 w-full flex-col divide-y overflow-y-auto rounded-xl border shadow-md", children: filteredUsers.slice(0, 5).map((u) => /* @__PURE__ */ jsxs3("div", { className: "hover:bg-primary-light/50 flex cursor-pointer items-center justify-between px-3.5 py-2.5 text-xs transition-colors", onClick: () => addMember(u.email, u.displayName, "user"), children: [
1202
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2.5", children: [
1203
+ /* @__PURE__ */ jsx5("span", { className: "bg-primary-light text-primary ring-primary/10 flex h-7 w-7 items-center justify-center rounded-full text-[10px] font-semibold ring-1", children: getInitials(u.displayName) }),
1204
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-col", children: [
1205
+ /* @__PURE__ */ jsx5("span", { className: "text-text font-medium", children: u.displayName }),
1206
+ /* @__PURE__ */ jsx5("span", { className: "text-text-muted text-[10px]", children: u.email })
1207
+ ] })
1208
+ ] }),
1209
+ /* @__PURE__ */ jsx5(Plus2, { size: 14, className: "text-primary/75" })
1210
+ ] }, u.id)) })
1211
+ ] }),
1212
+ /* @__PURE__ */ jsx5("div", { className: "mt-1 flex flex-col gap-1.5", children: invitedUsers.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "bg-bg/40 border-border/40 flex items-center justify-center rounded-xl border border-dashed py-4 text-center", children: /* @__PURE__ */ jsx5("p", { className: "text-text-muted text-xs italic", children: "Aucun utilisateur invit\xE9 pour le moment." }) }) : invitedUsers.map((m) => /* @__PURE__ */ jsxs3("div", { className: "bg-bg-subtle hover:bg-bg border-border/30 flex items-center gap-3 rounded-xl border p-2.5 transition-all duration-200", children: [
1213
+ /* @__PURE__ */ jsx5(MemberAvatar, { memberName: m.memberName, memberType: "user", avatarUrl: avatarMap[m.memberId.toLowerCase()], size: "sm", variant: m.role === "owner" ? "owner" : "primary" }),
1214
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
1215
+ /* @__PURE__ */ jsx5("div", { className: "text-text truncate text-xs font-semibold", children: m.memberName }),
1216
+ /* @__PURE__ */ jsx5("div", { className: "text-text-muted truncate text-[10px]", children: m.memberId })
1217
+ ] }),
1218
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
1219
+ /* @__PURE__ */ jsxs3("select", { className: "border-border bg-surface text-text focus:border-primary rounded-lg border px-2 py-1 text-[10px] font-medium focus:outline-none", value: m.role, onChange: (e) => setMembers(members.map((x) => x.memberId === m.memberId ? { ...x, role: e.target.value } : x)), children: [
1220
+ /* @__PURE__ */ jsx5("option", { value: "owner", children: "Propri\xE9taire" }),
1221
+ /* @__PURE__ */ jsx5("option", { value: "participant", children: "Participant" }),
1222
+ /* @__PURE__ */ jsx5("option", { value: "viewer", children: "Observateur" })
1223
+ ] }),
1224
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "text-text-muted hover:text-danger hover:bg-danger-light/20 rounded-lg p-1.5 transition-colors", onClick: () => removeMember(m.memberId), children: /* @__PURE__ */ jsx5(X3, { size: 14 }) })
1225
+ ] })
1226
+ ] }, m.memberId)) })
1227
+ ] }),
1228
+ /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-2", children: [
1229
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
1230
+ /* @__PURE__ */ jsxs3("label", { className: "text-text-muted flex items-center gap-1.5 text-xs font-semibold tracking-wider uppercase", children: [
1231
+ /* @__PURE__ */ jsx5(Brain3, { size: 14, className: "text-primary" }),
1232
+ " Agents IA"
1233
+ ] }),
1234
+ /* @__PURE__ */ jsxs3("span", { className: "bg-primary-light text-primary rounded-full px-2 py-0.5 text-[10px] font-bold", children: [
1235
+ invitedAgents.length,
1236
+ "/",
1237
+ agents.length
1238
+ ] })
1239
+ ] }),
1240
+ /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
1241
+ /* @__PURE__ */ jsxs3("div", { className: cn("border-border bg-bg focus-within:border-primary focus-within:ring-primary/10 flex min-h-[44px] cursor-text flex-wrap items-center gap-1.5 rounded-xl border px-3 py-1.5 text-sm transition-all focus-within:ring-2", agentSearchFocused && "border-primary ring-primary/10 ring-2"), onClick: () => agentSearchInputRef.current?.focus(), children: [
1242
+ invitedAgents.map((m) => /* @__PURE__ */ jsxs3("span", { className: "bg-primary-light/50 border-primary/20 text-primary flex items-center gap-1 rounded-lg border py-1 pr-1.5 pl-2 text-xs font-medium", children: [
1243
+ /* @__PURE__ */ jsx5(Brain3, { size: 10, className: "text-primary/80" }),
1244
+ m.memberName,
1245
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "hover:bg-primary-light text-primary rounded-md p-0.5 transition-colors", onClick: (e) => {
1246
+ e.stopPropagation();
1247
+ removeMember(m.memberId);
1248
+ }, children: /* @__PURE__ */ jsx5(X3, { size: 10 }) })
1249
+ ] }, m.memberId)),
1250
+ /* @__PURE__ */ jsx5("input", { ref: agentSearchInputRef, type: "search", name: "workspace-agent-search", autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": "true", className: "text-text placeholder:text-text-muted/65 min-w-[100px] flex-1 border-none bg-transparent p-0.5 text-xs focus:ring-0 focus:outline-none", placeholder: invitedAgents.length === 0 ? "Rechercher et inviter des agents..." : "", value: agentSearch, onChange: (e) => {
1251
+ setAgentSearch(e.target.value);
1252
+ setShowAgentDropdown(true);
1253
+ }, onFocus: () => {
1254
+ setAgentSearchFocused(true);
1255
+ setShowAgentDropdown(true);
1256
+ }, onBlur: () => setTimeout(() => {
1257
+ setAgentSearchFocused(false);
1258
+ setShowAgentDropdown(false);
1259
+ }, 200) })
1260
+ ] }),
1261
+ showAgentDropdown && /* @__PURE__ */ jsx5("div", { className: "border-border bg-surface divide-border/30 absolute z-20 mt-1.5 flex max-h-48 w-full flex-col divide-y overflow-y-auto rounded-xl border shadow-lg", children: unselectedAgents.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "text-text-muted p-3 text-center text-xs italic", children: agentSearch.trim().length > 0 ? "Aucun agent ne correspond." : "Tous les agents ont \xE9t\xE9 invit\xE9s." }) : unselectedAgents.map((agent) => /* @__PURE__ */ jsxs3("div", { className: "hover:bg-primary-light/50 flex cursor-pointer items-center justify-between px-3.5 py-2.5 text-xs transition-colors", onClick: () => {
1262
+ addMember(agent.agentId, agent.agentName, "agent");
1263
+ setAgentSearch("");
1264
+ }, children: [
1265
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2.5", children: [
1266
+ /* @__PURE__ */ jsx5("span", { className: "bg-surface border-border flex h-7 w-7 items-center justify-center rounded-full border", children: /* @__PURE__ */ jsx5(Brain3, { size: 12, className: "text-primary" }) }),
1267
+ /* @__PURE__ */ jsx5("span", { className: "text-text font-medium", children: agent.agentName })
1268
+ ] }),
1269
+ /* @__PURE__ */ jsx5(Plus2, { size: 14, className: "text-primary/75" })
1270
+ ] }, agent.agentId)) })
1271
+ ] })
1272
+ ] })
1273
+ ] }),
1274
+ step === 3 && /* @__PURE__ */ jsxs3("div", { className: "flex flex-col gap-3", children: [
1275
+ /* @__PURE__ */ jsxs3("div", { className: "bg-bg rounded-xl p-4", children: [
1276
+ /* @__PURE__ */ jsx5("div", { className: "mb-3 flex items-center gap-2", children: /* @__PURE__ */ jsxs3("div", { children: [
1277
+ /* @__PURE__ */ jsx5("div", { className: "text-text text-sm font-semibold", children: name || "Sans nom" }),
1278
+ /* @__PURE__ */ jsxs3("div", { className: "text-text-muted text-xs", children: [
1279
+ selectedType?.name ?? "\u2014",
1280
+ " \u2022 ",
1281
+ scope || "Sans scope"
1282
+ ] })
1283
+ ] }) }),
1284
+ entities.filter((e) => e.key.trim() && e.value.trim()).length > 0 && /* @__PURE__ */ jsx5("div", { className: "mb-2 flex flex-wrap gap-1", children: entities.filter((e) => e.key.trim() && e.value.trim()).map((e, i) => /* @__PURE__ */ jsxs3("span", { className: "border-border inline-flex items-center gap-1 rounded border px-2 py-0.5 text-xs", children: [
1285
+ e.isSecret && /* @__PURE__ */ jsx5(Lock, { size: 10, className: "text-warning shrink-0" }),
1286
+ /* @__PURE__ */ jsxs3("span", { className: "text-text-muted mr-1 font-medium", children: [
1287
+ e.key,
1288
+ ":"
1289
+ ] }),
1290
+ /* @__PURE__ */ jsx5("span", { className: "text-text", children: e.isSecret ? "\u2022\u2022\u2022\u2022\u2022\u2022" : e.value })
1291
+ ] }, i)) }),
1292
+ /* @__PURE__ */ jsxs3("div", { className: "text-text-muted flex items-center gap-3 text-xs", children: [
1293
+ /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-1", children: [
1294
+ /* @__PURE__ */ jsx5(Users2, { size: 12 }),
1295
+ " ",
1296
+ members.length,
1297
+ " membre",
1298
+ members.length > 1 ? "s" : ""
1299
+ ] }),
1300
+ syncWithSlack && /* @__PURE__ */ jsxs3("span", { className: "text-primary flex items-center gap-1 font-medium", children: [
1301
+ /* @__PURE__ */ jsx5(Slack, { size: 12 }),
1302
+ " Synchronis\xE9 avec Slack"
1303
+ ] })
1304
+ ] }),
1305
+ members.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "border-border/30 mt-3 border-t pt-3", children: [
1306
+ /* @__PURE__ */ jsxs3("span", { className: "text-text-muted mb-2 block text-[10px] font-bold tracking-wider uppercase", children: [
1307
+ "Membres et Agents invit\xE9s (",
1308
+ members.length,
1309
+ ")"
1310
+ ] }),
1311
+ /* @__PURE__ */ jsx5("div", { className: "flex max-h-40 flex-col gap-1.5 overflow-y-auto pr-1", children: members.map((m) => /* @__PURE__ */ jsxs3("div", { className: "bg-bg-subtle/50 flex items-center gap-2.5 rounded-lg p-2 text-xs", children: [
1312
+ /* @__PURE__ */ jsx5(MemberAvatar, { memberName: m.memberName, memberType: m.memberType, avatarUrl: m.memberType === "user" ? avatarMap[m.memberId.toLowerCase()] : void 0, size: "xs", variant: m.role === "owner" ? "owner" : "primary" }),
1313
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
1314
+ /* @__PURE__ */ jsx5("span", { className: "text-text truncate font-medium", children: m.memberName }),
1315
+ /* @__PURE__ */ jsxs3("span", { className: "text-text-muted ml-2 text-[10px]", children: [
1316
+ "(",
1317
+ m.memberType === "agent" ? "Agent" : m.role === "owner" ? "Propri\xE9taire" : m.role === "viewer" ? "Observateur" : "Participant",
1318
+ ")"
1319
+ ] })
1320
+ ] })
1321
+ ] }, m.memberId)) })
1322
+ ] })
1323
+ ] }),
1324
+ error && /* @__PURE__ */ jsx5("div", { className: "bg-danger-light text-danger rounded-lg px-3 py-2 text-xs", children: error })
1325
+ ] })
1326
+ ] }),
1327
+ /* @__PURE__ */ jsxs3("div", { className: "border-border flex shrink-0 items-center justify-between border-t px-6 py-4", children: [
1328
+ /* @__PURE__ */ jsxs3("button", { type: "button", className: cn("text-text-muted hover:text-text flex items-center gap-1 text-sm transition-colors", step === 0 && "invisible"), onClick: () => setStep((s) => s - 1), disabled: loading, children: [
1329
+ /* @__PURE__ */ jsx5(ArrowLeft, { size: 14 }),
1330
+ " Pr\xE9c\xE9dent"
1331
+ ] }),
1332
+ step < 3 ? /* @__PURE__ */ jsxs3("button", { type: "button", className: cn("flex items-center gap-1 rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors", canAdvance() ? "bg-primary hover:bg-primary-hover" : "bg-border cursor-not-allowed"), onClick: () => setStep((s) => s + 1), disabled: !canAdvance(), children: [
1333
+ "Suivant ",
1334
+ /* @__PURE__ */ jsx5(ArrowRight, { size: 14 })
1335
+ ] }) : /* @__PURE__ */ jsxs3("button", { type: "button", className: "bg-primary hover:bg-primary-hover flex items-center gap-1 rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50", onClick: handleCreate, disabled: loading, children: [
1336
+ loading ? /* @__PURE__ */ jsx5(Loader22, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx5(Check2, { size: 14 }),
1337
+ " Cr\xE9er le workspace"
1338
+ ] })
1339
+ ] })
1340
+ ] });
1341
+ };
1342
+
1343
+ // src/components/CreatePrivateWorkspaceModal.tsx
1344
+ import { useState as useState4, useEffect as useEffect4, useMemo as useMemo5, useId as useId3, useRef as useRef2 } from "react";
1345
+ import { X as X4, Loader2 as Loader23, Lock as Lock2, MessageSquare, Search as Search2 } from "lucide-react";
1346
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1347
+ var CreatePrivateWorkspaceModal = ({ open, onClose, onCreated }) => {
1348
+ const titleId = useId3();
1349
+ const { config } = useWorkspaceContext();
1350
+ const apis = useWorkspaceApi();
1351
+ const [loading, setLoading] = useState4(false);
1352
+ const [error, setError] = useState4(null);
1353
+ const [search, setSearch] = useState4("");
1354
+ const [searchResults, setSearchResults] = useState4([]);
1355
+ const [searchingUsers, setSearchingUsers] = useState4(false);
1356
+ const [selectedUser, setSelectedUser] = useState4(null);
1357
+ const searchInputRef = useRef2(null);
1358
+ useEffect4(() => {
1359
+ if (search.length < 2 || !config.searchUsers) {
1360
+ setSearchResults([]);
1361
+ return;
1362
+ }
1363
+ let cancelled = false;
1364
+ setSearchingUsers(true);
1365
+ config.searchUsers(search).then((r) => {
1366
+ if (!cancelled) setSearchResults(r);
1367
+ }).catch(() => {
1368
+ if (!cancelled) setSearchResults([]);
1369
+ }).finally(() => {
1370
+ if (!cancelled) setSearchingUsers(false);
1371
+ });
1372
+ return () => {
1373
+ cancelled = true;
1374
+ };
1375
+ }, [search]);
1376
+ const filteredUsers = useMemo5(() => searchResults.filter((u) => u.email !== config.currentUserId), [searchResults, config.currentUserId]);
1377
+ useEffect4(() => {
1378
+ if (!open) {
1379
+ setSearch("");
1380
+ setSelectedUser(null);
1381
+ setError(null);
1382
+ setLoading(false);
1383
+ } else {
1384
+ setTimeout(() => searchInputRef.current?.focus(), 100);
1385
+ }
1386
+ }, [open]);
1387
+ const handleCreate = async () => {
1388
+ if (!selectedUser || !config.currentUserId) return;
1389
+ setLoading(true);
1390
+ setError(null);
1391
+ try {
1392
+ const payload = {
1393
+ name: `${config.currentUserName ?? config.currentUserId} \u2194 ${selectedUser.displayName}`,
1394
+ isPrivate: true,
1395
+ members: [{ memberId: selectedUser.email, memberName: selectedUser.displayName, memberType: "user", role: "participant" }]
1396
+ };
1397
+ const ws = await apis.workspaceApi.create(payload);
1398
+ onCreated?.(ws);
1399
+ onClose();
1400
+ } catch (err) {
1401
+ setError(err.message);
1402
+ } finally {
1403
+ setLoading(false);
1404
+ }
1405
+ };
1406
+ return /* @__PURE__ */ jsxs4(Modal, { open, onClose, zIndexClass: "z-50", size: "sm", closeOnBackdropClick: false, backdropClassName: "bg-black/40", panelClassName: "bg-surface border-border flex max-h-[min(70dvh,500px)] flex-col overflow-hidden rounded-2xl border shadow-xl", headingId: titleId, ariaLabelledBy: titleId, title: "Conversation Priv\xE9e", description: /* @__PURE__ */ jsx6("p", { className: "text-text-muted text-xs", children: "Choisissez un utilisateur pour d\xE9marrer une conversation priv\xE9e." }), headerClassName: "items-center", closeButtonClassName: "text-text-muted hover:text-text transition-colors", titleIcon: /* @__PURE__ */ jsx6("span", { className: "bg-primary-light text-primary flex h-8 w-8 items-center justify-center rounded-lg", children: /* @__PURE__ */ jsx6(Lock2, { size: 16 }) }), children: [
1407
+ /* @__PURE__ */ jsxs4("div", { className: "min-h-0 flex-1 overflow-y-auto px-6 py-4", children: [
1408
+ selectedUser ? /* @__PURE__ */ jsx6("div", { className: "mb-4", children: /* @__PURE__ */ jsxs4("div", { className: "bg-primary-light/30 border-primary/20 flex items-center gap-3 rounded-xl border p-3", children: [
1409
+ /* @__PURE__ */ jsx6(MemberAvatar, { memberName: selectedUser.displayName, memberType: "user", avatarUrl: selectedUser.avatarUrl, size: "md", variant: "primary" }),
1410
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0 flex-1", children: [
1411
+ /* @__PURE__ */ jsx6("div", { className: "text-text text-sm font-semibold", children: selectedUser.displayName }),
1412
+ /* @__PURE__ */ jsx6("div", { className: "text-text-muted text-xs", children: selectedUser.email })
1413
+ ] }),
1414
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "text-text-muted hover:text-danger rounded p-1 transition-colors", onClick: () => setSelectedUser(null), children: /* @__PURE__ */ jsx6(X4, { size: 16 }) })
1415
+ ] }) }) : /* @__PURE__ */ jsxs4("div", { children: [
1416
+ /* @__PURE__ */ jsxs4("div", { className: "relative", children: [
1417
+ /* @__PURE__ */ jsx6(Search2, { size: 14, className: "text-text-muted absolute top-1/2 left-3 -translate-y-1/2" }),
1418
+ /* @__PURE__ */ jsx6("input", { ref: searchInputRef, type: "search", autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": "true", className: "border-border bg-bg text-text focus:border-primary focus:ring-primary/10 placeholder:text-text-muted/65 w-full rounded-xl border py-2.5 pr-3 pl-9 text-sm transition-all focus:ring-2 focus:outline-none", placeholder: "Rechercher un utilisateur...", value: search, onChange: (e) => setSearch(e.target.value) }),
1419
+ search.length >= 2 && searchingUsers && /* @__PURE__ */ jsx6("div", { className: "absolute top-1/2 right-3 -translate-y-1/2", children: /* @__PURE__ */ jsx6(Loader23, { size: 16, className: "text-primary animate-spin" }) })
1420
+ ] }),
1421
+ search.length >= 2 && !searchingUsers && filteredUsers.length > 0 && /* @__PURE__ */ jsx6("div", { className: "border-border bg-surface mt-2 flex max-h-48 flex-col overflow-y-auto rounded-xl border shadow-sm", children: filteredUsers.slice(0, 8).map((u) => /* @__PURE__ */ jsxs4("button", { type: "button", className: "hover:bg-primary-light/50 flex items-center gap-3 px-3.5 py-2.5 text-left transition-colors first:rounded-t-xl last:rounded-b-xl", onClick: () => {
1422
+ setSelectedUser(u);
1423
+ setSearch("");
1424
+ }, children: [
1425
+ /* @__PURE__ */ jsx6("span", { className: "bg-primary-light text-primary flex h-8 w-8 items-center justify-center rounded-full text-xs font-semibold", children: getInitials(u.displayName) }),
1426
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0 flex-1", children: [
1427
+ /* @__PURE__ */ jsx6("div", { className: "text-text text-sm font-medium", children: u.displayName }),
1428
+ /* @__PURE__ */ jsx6("div", { className: "text-text-muted text-[11px]", children: u.email })
1429
+ ] })
1430
+ ] }, u.id)) }),
1431
+ search.length >= 2 && !searchingUsers && filteredUsers.length === 0 && /* @__PURE__ */ jsx6("p", { className: "text-text-muted mt-3 text-center text-xs italic", children: "Aucun utilisateur trouv\xE9." }),
1432
+ search.length < 2 && /* @__PURE__ */ jsx6("p", { className: "text-text-muted mt-4 text-center text-xs", children: "Saisissez au moins 2 caract\xE8res pour rechercher." })
1433
+ ] }),
1434
+ error && /* @__PURE__ */ jsx6("div", { className: "bg-danger-light text-danger mt-3 rounded-lg px-3 py-2 text-xs", children: error })
1435
+ ] }),
1436
+ /* @__PURE__ */ jsxs4("div", { className: "border-border flex shrink-0 items-center justify-end gap-2 border-t px-6 py-4", children: [
1437
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "text-text-muted hover:text-text text-sm transition-colors", onClick: onClose, disabled: loading, children: "Annuler" }),
1438
+ /* @__PURE__ */ jsxs4("button", { type: "button", className: cn("flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50", selectedUser ? "bg-primary hover:bg-primary-hover" : "bg-border cursor-not-allowed"), onClick: handleCreate, disabled: !selectedUser || loading, children: [
1439
+ loading ? /* @__PURE__ */ jsx6(Loader23, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsx6(MessageSquare, { size: 14 }),
1440
+ " D\xE9marrer"
1441
+ ] })
1442
+ ] })
1443
+ ] });
1444
+ };
1445
+
1446
+ // src/components/SyncPanel.tsx
1447
+ import { useState as useState5, useEffect as useEffect5, useCallback } from "react";
1448
+ import {
1449
+ RefreshCw,
1450
+ ExternalLink,
1451
+ Info,
1452
+ CheckCircle2,
1453
+ XCircle,
1454
+ Loader2 as Loader24,
1455
+ Hash,
1456
+ AlertTriangle
1457
+ } from "lucide-react";
1458
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1459
+ var SLACK_COLOR = "#4A154B";
1460
+ var SYNC_INFO = {
1461
+ title: "Comment fonctionne la synchronisation ?",
1462
+ description: "La synchronisation bidirectionnelle relie ce workspace \xE0 un canal externe (Slack, Teams\u2026). Les messages, fichiers et membres sont automatiquement r\xE9pliqu\xE9s dans les deux sens.",
1463
+ features: [
1464
+ "Messages envoy\xE9s depuis le workspace sont r\xE9pliqu\xE9s vers Slack",
1465
+ "Messages envoy\xE9s depuis Slack sont r\xE9pliqu\xE9s vers le workspace",
1466
+ "Fichiers et artefacts sont synchronis\xE9s dans les deux sens",
1467
+ "Les nouveaux membres ajout\xE9s au workspace sont invit\xE9s dans Slack",
1468
+ "Les mentions Slack sont converties en mentions workspace",
1469
+ "D\xE9sactiver la synchronisation conserve le canal Slack ouvert (pas d'archivage)"
1470
+ ],
1471
+ limitations: [
1472
+ "Les messages initialement post\xE9s par un utilisateur r\xE9el dans Slack ne peuvent pas \xEAtre modifi\xE9s/supprim\xE9s depuis le workspace (limitation API Slack)",
1473
+ "Le bot Slack doit avoir les scopes n\xE9cessaires (channels:*, chat:write, files:read, users:read.email)",
1474
+ "Le listener Socket Mode doit tourner sur une seule instance en production",
1475
+ "Seuls les utilisateurs ayant le m\xEAme email dans Slack et le workspace sont synchronis\xE9s"
1476
+ ]
1477
+ };
1478
+ var SyncPanel = ({
1479
+ workspaceId,
1480
+ syncWithSlack,
1481
+ isOwner,
1482
+ slackConfigured = true,
1483
+ onToggleSync
1484
+ }) => {
1485
+ const [syncStatus, setSyncStatus] = useState5(null);
1486
+ const [loading, setLoading] = useState5(true);
1487
+ const [toggling, setToggling] = useState5(false);
1488
+ const [infoOpen, setInfoOpen] = useState5(false);
1489
+ const apis = useWorkspaceApi();
1490
+ const fetchSyncStatus = useCallback(async () => {
1491
+ try {
1492
+ const status = await apis.syncApi.getStatus(workspaceId);
1493
+ setSyncStatus(status);
1494
+ } catch (err) {
1495
+ console.error("Failed to fetch sync status:", err);
1496
+ } finally {
1497
+ setLoading(false);
1498
+ }
1499
+ }, [workspaceId]);
1500
+ useEffect5(() => {
1501
+ fetchSyncStatus();
1502
+ }, [fetchSyncStatus]);
1503
+ useEffect5(() => {
1504
+ if (syncStatus) {
1505
+ setSyncStatus(
1506
+ (prev) => prev ? { ...prev, syncWithSlack: syncWithSlack ?? false } : prev
1507
+ );
1508
+ }
1509
+ }, [syncWithSlack]);
1510
+ const handleToggle = async (provider, enabled) => {
1511
+ setToggling(true);
1512
+ try {
1513
+ await onToggleSync?.(provider, enabled);
1514
+ await fetchSyncStatus();
1515
+ } catch (err) {
1516
+ console.error("Failed to toggle sync:", err);
1517
+ } finally {
1518
+ setToggling(false);
1519
+ }
1520
+ };
1521
+ const slackLink = syncStatus?.links?.find((l) => l.provider === "slack");
1522
+ const isSlackActive = syncStatus?.syncWithSlack ?? syncWithSlack ?? false;
1523
+ if (loading) {
1524
+ return /* @__PURE__ */ jsx7("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx7(Loader24, { size: 20, className: "text-primary animate-spin" }) });
1525
+ }
1526
+ return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4", children: [
1527
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1528
+ /* @__PURE__ */ jsx7("span", { className: "text-text-muted text-xs font-semibold tracking-wider uppercase", children: "Synchronisation" }),
1529
+ /* @__PURE__ */ jsxs5(
1530
+ "button",
1531
+ {
1532
+ className: "text-text-muted hover:text-primary flex items-center gap-1 text-xs transition-colors",
1533
+ onClick: () => fetchSyncStatus(),
1534
+ title: "Rafra\xEEchir",
1535
+ children: [
1536
+ /* @__PURE__ */ jsx7(RefreshCw, { size: 11 }),
1537
+ " Rafra\xEEchir"
1538
+ ]
1539
+ }
1540
+ )
1541
+ ] }),
1542
+ /* @__PURE__ */ jsxs5("div", { className: cn("bg-bg border-border rounded-lg border", !slackConfigured && "opacity-60"), children: [
1543
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between p-3", children: [
1544
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2.5", children: [
1545
+ /* @__PURE__ */ jsx7(
1546
+ "div",
1547
+ {
1548
+ className: "flex h-8 w-8 items-center justify-center rounded-lg",
1549
+ style: { backgroundColor: SLACK_COLOR },
1550
+ children: /* @__PURE__ */ jsx7(Hash, { size: 16, className: "text-white" })
1551
+ }
1552
+ ),
1553
+ /* @__PURE__ */ jsxs5("div", { children: [
1554
+ /* @__PURE__ */ jsxs5("div", { className: "text-text flex items-center gap-1.5 text-sm font-medium", children: [
1555
+ "Slack",
1556
+ !slackConfigured ? /* @__PURE__ */ jsx7(XCircle, { size: 14, className: "text-text-muted" }) : isSlackActive ? /* @__PURE__ */ jsx7(CheckCircle2, { size: 14, className: "text-success" }) : /* @__PURE__ */ jsx7(XCircle, { size: 14, className: "text-text-muted" })
1557
+ ] }),
1558
+ /* @__PURE__ */ jsx7("span", { className: "text-text-muted text-[10px]", children: !slackConfigured ? "Non configur\xE9" : isSlackActive ? "Synchronisation active" : "Non synchronis\xE9" })
1559
+ ] })
1560
+ ] }),
1561
+ slackConfigured && isOwner && /* @__PURE__ */ jsx7(
1562
+ "button",
1563
+ {
1564
+ className: `rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${isSlackActive ? "bg-danger-light text-danger hover:bg-danger/20" : "bg-success-light text-success hover:bg-success/20"}`,
1565
+ onClick: () => handleToggle("slack", !isSlackActive),
1566
+ disabled: toggling,
1567
+ children: toggling ? /* @__PURE__ */ jsx7(Loader24, { size: 12, className: "animate-spin" }) : isSlackActive ? "D\xE9sactiver" : "Activer"
1568
+ }
1569
+ ),
1570
+ !slackConfigured && /* @__PURE__ */ jsx7("span", { className: "bg-bg border-border rounded border px-2 py-1 text-[10px] font-medium text-text-muted", children: "Non disponible" })
1571
+ ] }),
1572
+ !slackConfigured && /* @__PURE__ */ jsx7("div", { className: "border-border border-t px-3 py-2.5", children: /* @__PURE__ */ jsxs5("div", { className: "text-text-muted flex items-center gap-2 text-xs", children: [
1573
+ /* @__PURE__ */ jsx7(AlertTriangle, { size: 14, className: "flex-shrink-0 text-warning" }),
1574
+ /* @__PURE__ */ jsx7("span", { children: "La synchronisation Slack n'est pas configur\xE9e sur le serveur. Contactez un administrateur pour l'activer." })
1575
+ ] }) }),
1576
+ slackConfigured && slackLink && /* @__PURE__ */ jsx7("div", { className: "border-border border-t px-3 py-2.5", children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1.5", children: [
1577
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1578
+ /* @__PURE__ */ jsx7("span", { className: "text-text-muted text-[10px] font-medium uppercase tracking-wider", children: "Canal externe" }),
1579
+ /* @__PURE__ */ jsx7(ExternalLink, { size: 10, className: "text-text-muted" })
1580
+ ] }),
1581
+ /* @__PURE__ */ jsxs5("div", { className: "bg-surface border-border rounded border p-2", children: [
1582
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-xs", children: [
1583
+ /* @__PURE__ */ jsx7(Hash, { size: 12, className: "text-text-muted flex-shrink-0" }),
1584
+ /* @__PURE__ */ jsx7("span", { className: "text-text font-medium", children: slackLink.externalName || slackLink.externalId })
1585
+ ] }),
1586
+ /* @__PURE__ */ jsxs5("div", { className: "text-text-muted mt-1 flex items-center gap-2 text-[10px]", children: [
1587
+ /* @__PURE__ */ jsxs5("span", { children: [
1588
+ "ID: ",
1589
+ slackLink.externalId
1590
+ ] }),
1591
+ slackLink.externalTeamId && /* @__PURE__ */ jsxs5(Fragment2, { children: [
1592
+ /* @__PURE__ */ jsx7("span", { children: "\u2022" }),
1593
+ /* @__PURE__ */ jsxs5("span", { children: [
1594
+ "Team: ",
1595
+ slackLink.externalTeamId
1596
+ ] })
1597
+ ] })
1598
+ ] }),
1599
+ /* @__PURE__ */ jsxs5("div", { className: "text-text-muted mt-1 flex items-center gap-2 text-[10px]", children: [
1600
+ /* @__PURE__ */ jsx7(
1601
+ "span",
1602
+ {
1603
+ className: `inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[9px] font-medium ${isSlackActive ? "bg-success-light text-success" : "bg-warning-light text-warning"}`,
1604
+ children: isSlackActive ? "Sync active" : "Sync en pause"
1605
+ }
1606
+ ),
1607
+ /* @__PURE__ */ jsxs5("span", { children: [
1608
+ "Cr\xE9\xE9 le",
1609
+ " ",
1610
+ new Date(slackLink.createdAt).toLocaleDateString("fr-FR")
1611
+ ] })
1612
+ ] }),
1613
+ !isSlackActive && /* @__PURE__ */ jsx7("div", { className: "text-text-muted mt-1.5 text-[10px] italic", children: "Le canal Slack reste ouvert \u2014 seule la synchronisation est en pause." })
1614
+ ] })
1615
+ ] }) }),
1616
+ slackConfigured && isSlackActive && !slackLink && /* @__PURE__ */ jsx7("div", { className: "border-border border-t px-3 py-2.5", children: /* @__PURE__ */ jsxs5("div", { className: "text-warning bg-warning-light flex items-center gap-2 rounded-lg p-2 text-xs", children: [
1617
+ /* @__PURE__ */ jsx7(AlertTriangle, { size: 14, className: "flex-shrink-0" }),
1618
+ /* @__PURE__ */ jsx7("span", { children: "La synchronisation est activ\xE9e mais aucun canal Slack n'est encore li\xE9. Le canal sera cr\xE9\xE9 automatiquement." })
1619
+ ] }) })
1620
+ ] }),
1621
+ /* @__PURE__ */ jsx7("div", { className: "bg-bg border-border rounded-lg border opacity-50", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between p-3", children: [
1622
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2.5", children: [
1623
+ /* @__PURE__ */ jsx7("div", { className: "bg-info flex h-8 w-8 items-center justify-center rounded-lg", children: /* @__PURE__ */ jsx7("span", { className: "text-xs font-bold text-white", children: "T" }) }),
1624
+ /* @__PURE__ */ jsxs5("div", { children: [
1625
+ /* @__PURE__ */ jsxs5("div", { className: "text-text flex items-center gap-1.5 text-sm font-medium", children: [
1626
+ "Microsoft Teams",
1627
+ /* @__PURE__ */ jsx7(XCircle, { size: 14, className: "text-text-muted" })
1628
+ ] }),
1629
+ /* @__PURE__ */ jsx7("span", { className: "text-text-muted text-[10px]", children: "Bient\xF4t disponible" })
1630
+ ] })
1631
+ ] }),
1632
+ /* @__PURE__ */ jsx7("span", { className: "bg-bg border-border rounded border px-2 py-1 text-[10px] font-medium text-text-muted", children: "Prochainement" })
1633
+ ] }) }),
1634
+ /* @__PURE__ */ jsxs5("div", { className: "bg-bg border-border rounded-lg border", children: [
1635
+ /* @__PURE__ */ jsxs5(
1636
+ "button",
1637
+ {
1638
+ className: "flex w-full items-center gap-2 p-3 text-left",
1639
+ onClick: () => setInfoOpen(!infoOpen),
1640
+ children: [
1641
+ /* @__PURE__ */ jsx7(Info, { size: 14, className: "text-info flex-shrink-0" }),
1642
+ /* @__PURE__ */ jsx7("span", { className: "text-text text-xs font-medium", children: SYNC_INFO.title }),
1643
+ /* @__PURE__ */ jsx7(
1644
+ "span",
1645
+ {
1646
+ className: `text-text-muted ml-auto text-[10px] transition-transform ${infoOpen ? "rotate-180" : ""}`,
1647
+ children: "\u25BE"
1648
+ }
1649
+ )
1650
+ ]
1651
+ }
1652
+ ),
1653
+ infoOpen && /* @__PURE__ */ jsxs5("div", { className: "border-border border-t px-3 pb-3 pt-2", children: [
1654
+ /* @__PURE__ */ jsx7("p", { className: "text-text-muted mb-3 text-xs leading-relaxed", children: SYNC_INFO.description }),
1655
+ /* @__PURE__ */ jsxs5("div", { className: "mb-3", children: [
1656
+ /* @__PURE__ */ jsx7("span", { className: "text-text mb-1.5 block text-[10px] font-semibold uppercase tracking-wider", children: "Fonctionnalit\xE9s" }),
1657
+ /* @__PURE__ */ jsx7("ul", { className: "flex flex-col gap-1", children: SYNC_INFO.features.map((f, i) => /* @__PURE__ */ jsxs5(
1658
+ "li",
1659
+ {
1660
+ className: "text-text-muted flex items-start gap-1.5 text-xs",
1661
+ children: [
1662
+ /* @__PURE__ */ jsx7(
1663
+ CheckCircle2,
1664
+ {
1665
+ size: 12,
1666
+ className: "text-success mt-0.5 flex-shrink-0"
1667
+ }
1668
+ ),
1669
+ f
1670
+ ]
1671
+ },
1672
+ i
1673
+ )) })
1674
+ ] }),
1675
+ /* @__PURE__ */ jsxs5("div", { children: [
1676
+ /* @__PURE__ */ jsx7("span", { className: "text-text mb-1.5 block text-[10px] font-semibold uppercase tracking-wider", children: "Limitations connues" }),
1677
+ /* @__PURE__ */ jsx7("ul", { className: "flex flex-col gap-1", children: SYNC_INFO.limitations.map((l, i) => /* @__PURE__ */ jsxs5(
1678
+ "li",
1679
+ {
1680
+ className: "text-text-muted flex items-start gap-1.5 text-xs",
1681
+ children: [
1682
+ /* @__PURE__ */ jsx7(
1683
+ AlertTriangle,
1684
+ {
1685
+ size: 12,
1686
+ className: "text-warning mt-0.5 flex-shrink-0"
1687
+ }
1688
+ ),
1689
+ l
1690
+ ]
1691
+ },
1692
+ i
1693
+ )) })
1694
+ ] })
1695
+ ] })
1696
+ ] })
1697
+ ] });
1698
+ };
1699
+
1700
+ // src/components/Pagination.tsx
1701
+ import {
1702
+ ChevronsLeft,
1703
+ ChevronLeft,
1704
+ ChevronRight,
1705
+ ChevronsRight
1706
+ } from "lucide-react";
1707
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1708
+ var NavButton = ({
1709
+ onClick,
1710
+ disabled,
1711
+ children,
1712
+ title
1713
+ }) => /* @__PURE__ */ jsx8(
1714
+ "button",
1715
+ {
1716
+ onClick,
1717
+ disabled,
1718
+ title,
1719
+ className: cn(
1720
+ "flex h-9 w-9 items-center justify-center rounded-lg border text-sm transition-all duration-200",
1721
+ disabled ? "border-border-light text-text-placeholder cursor-not-allowed opacity-40" : "border-border bg-surface text-text-secondary hover:bg-primary/8 hover:border-primary/30 hover:text-primary active:scale-95"
1722
+ ),
1723
+ children
1724
+ }
1725
+ );
1726
+ var Pagination = ({
1727
+ currentPage,
1728
+ totalPages,
1729
+ onPageChange,
1730
+ loading = false
1731
+ }) => {
1732
+ if (totalPages <= 1) return null;
1733
+ const getVisiblePages = () => {
1734
+ const pages2 = [];
1735
+ const maxVisible = 5;
1736
+ if (totalPages <= maxVisible + 2) {
1737
+ for (let i = 1; i <= totalPages; i++) pages2.push(i);
1738
+ return pages2;
1739
+ }
1740
+ pages2.push(1);
1741
+ const start = Math.max(2, currentPage - 1);
1742
+ const end = Math.min(totalPages - 1, currentPage + 1);
1743
+ if (start > 2) pages2.push("ellipsis");
1744
+ for (let i = start; i <= end; i++) pages2.push(i);
1745
+ if (end < totalPages - 1) pages2.push("ellipsis");
1746
+ pages2.push(totalPages);
1747
+ return pages2;
1748
+ };
1749
+ const pages = getVisiblePages();
1750
+ return /* @__PURE__ */ jsxs6("div", { className: "mt-6 flex items-center justify-center gap-1.5", children: [
1751
+ /* @__PURE__ */ jsx8(
1752
+ NavButton,
1753
+ {
1754
+ onClick: () => onPageChange(1),
1755
+ disabled: currentPage === 1 || loading,
1756
+ title: "Premi\xE8re page",
1757
+ children: /* @__PURE__ */ jsx8(ChevronsLeft, { size: 16 })
1758
+ }
1759
+ ),
1760
+ /* @__PURE__ */ jsx8(
1761
+ NavButton,
1762
+ {
1763
+ onClick: () => onPageChange(currentPage - 1),
1764
+ disabled: currentPage === 1 || loading,
1765
+ title: "Page pr\xE9c\xE9dente",
1766
+ children: /* @__PURE__ */ jsx8(ChevronLeft, { size: 16 })
1767
+ }
1768
+ ),
1769
+ pages.map(
1770
+ (p, idx) => p === "ellipsis" ? /* @__PURE__ */ jsx8(
1771
+ "span",
1772
+ {
1773
+ className: "text-text-muted flex h-9 w-9 items-center justify-center text-sm",
1774
+ children: "\u2026"
1775
+ },
1776
+ `ellipsis-${idx}`
1777
+ ) : /* @__PURE__ */ jsx8(
1778
+ "button",
1779
+ {
1780
+ onClick: () => onPageChange(p),
1781
+ disabled: loading,
1782
+ className: cn(
1783
+ "flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium transition-all duration-200",
1784
+ p === currentPage ? "bg-primary text-white shadow-sm shadow-primary/30" : "border-border bg-surface text-text-secondary hover:bg-primary/8 hover:border-primary/30 hover:text-primary border active:scale-95"
1785
+ ),
1786
+ children: p
1787
+ },
1788
+ p
1789
+ )
1790
+ ),
1791
+ /* @__PURE__ */ jsx8(
1792
+ NavButton,
1793
+ {
1794
+ onClick: () => onPageChange(currentPage + 1),
1795
+ disabled: currentPage === totalPages || loading,
1796
+ title: "Page suivante",
1797
+ children: /* @__PURE__ */ jsx8(ChevronRight, { size: 16 })
1798
+ }
1799
+ ),
1800
+ /* @__PURE__ */ jsx8(
1801
+ NavButton,
1802
+ {
1803
+ onClick: () => onPageChange(totalPages),
1804
+ disabled: currentPage === totalPages || loading,
1805
+ title: "Derni\xE8re page",
1806
+ children: /* @__PURE__ */ jsx8(ChevronsRight, { size: 16 })
1807
+ }
1808
+ )
1809
+ ] });
1810
+ };
1811
+ export {
1812
+ AVAILABLE_AGENTS,
1813
+ CATEGORY_STYLES,
1814
+ CreatePrivateWorkspaceModal,
1815
+ CreateWorkspaceModal,
1816
+ FILTER_PILLS,
1817
+ ICON_COLOR,
1818
+ IGNORED_FIELDS,
1819
+ MEMBER_ROLE_LABELS,
1820
+ MemberAvatar,
1821
+ MembersPanel,
1822
+ Modal,
1823
+ Pagination,
1824
+ ROLE_LABELS,
1825
+ STATUS_LABELS,
1826
+ SyncPanel,
1827
+ WORKSPACE_FIELD_LABELS,
1828
+ WORKSPACE_STATUS_LABELS,
1829
+ WorkspaceEvent,
1830
+ WorkspaceProvider,
1831
+ cn,
1832
+ createWorkspaceApis,
1833
+ createWorkspaceClient,
1834
+ createWorkspaceListStore,
1835
+ createWorkspaceSocket,
1836
+ formatWorkspaceExport,
1837
+ getInitials,
1838
+ useWorkspaceApi,
1839
+ useWorkspaceContext
1840
+ };
1841
+ //# sourceMappingURL=index.mjs.map