@arbidocs/react 0.4.3 → 0.4.4

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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { formatRelativeTime } from './chunk-EJ2BMHYK.js';
2
2
  export { DEFAULT_NOTIFY_TONE, NotificationBell, Toaster, formatRelativeTime, notify, seedNotifications, selectUnread, toast, useNotify } from './chunk-EJ2BMHYK.js';
3
- import { normalizeCitationMarkup, MarkMenu, AiMarkdown, Card, Button, Badge, TagEntity, MarkdownRenderer, Input, FILE_TYPE_COLORS, getEntityIcon, UserEntity, getEntityColorStyles, AgentBadge, Textarea, Slider, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DocumentEntity, DocumentCard, useAudioRecorder, MicWaveformIndicator, IconToggle, classifyMediaError, formatElapsed, PassageCard } from './chunk-MIJRORQL.js';
3
+ import { normalizeCitationMarkup, MarkMenu, AiMarkdown, Card, Button, Badge, TagEntity, MarkdownRenderer, Input, FILE_TYPE_COLORS, getEntityIcon, UserEntity, getEntityColorStyles, AgentBadge, Textarea, Label, Slider, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DocumentEntity, DocumentCard, useAudioRecorder, MicWaveformIndicator, IconToggle, classifyMediaError, formatElapsed, PassageCard } from './chunk-MIJRORQL.js';
4
4
  export { AgentBadge, AiMarkdown, ArbiBotHeadIcon, AudIcon, Avatar, AvatarFallback, AvatarImage, BASE_COLORS, Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DictateProvider, DocumentCard, DocumentEntity, ENTITY_MAPPINGS, EmlIcon, EntityIcon, EntityPill, FILE_TYPE_COLORS, HoverCard, HoverCardContent, HoverCardTrigger, IconToggle, ImgIcon, InlineMicButton, Input, Label, MarkMenu, MarkdownRenderer, MarkdownStreamingContext, MicRecordingOverlay, MicWaveformIndicator, NEUTRAL_GRAY, PLACEHOLDER_COLOR, PassageCard, PdfIcon, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PresenceLabel, Progress, ProjectEntity, RadioGroup, RadioGroupItem, RagDot, RelevanceDot, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Slider, StatusPill, Switch, TAG_TYPE_ICONS, TAG_TYPE_LABELS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, TagEntity, Textarea, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserEntity, UserMenu, VISIBILITY_ICONS, VidIcon, VisibilityIcon, badgeVariants, buttonVariants, cardVariants, configureEntityRegistry, configureRagLabels, configureStatusRegistry, formatElapsed, getDocIcon, getEntityColor, getEntityColorStyles, getEntityIcon, getEntityLabel, getStatusVariant, getTagTypeIcon, getTagTypeLabel, getWorkspaceColor, markdownBaseComponents, normalizeCitationMarkup, splitMarkdownBlocks, toggleVariants, useAudioRecorder } from './chunk-MIJRORQL.js';
5
5
  import { cn, ScrollArea, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './chunk-6ALMBER7.js';
6
6
  export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, ScrollArea, ScrollBar, cn } from './chunk-6ALMBER7.js';
@@ -23,6 +23,23 @@ import Cropper from 'react-easy-crop';
23
23
  import { AgGridReact } from 'ag-grid-react';
24
24
  import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community';
25
25
 
26
+ // src/credits.ts
27
+ var DEFAULT_USD_PER_CREDIT = 0.01;
28
+ function formatCredits(credits, options = {}) {
29
+ const { unit = true, locale = "en-US" } = options;
30
+ const whole = Math.round(Number.isFinite(credits) ? credits : 0);
31
+ const digits = whole.toLocaleString(locale);
32
+ return unit ? `${digits} ${whole === 1 ? "credit" : "credits"}` : digits;
33
+ }
34
+ function creditsToUsd(credits, usdPerCredit = DEFAULT_USD_PER_CREDIT) {
35
+ return credits * usdPerCredit;
36
+ }
37
+ function isUsageMetered(usage, subscription) {
38
+ const empty = usage.total_spend === 0 && usage.total_tokens === 0 && usage.total_requests === 0;
39
+ return !(empty && subscription === "dev");
40
+ }
41
+
42
+ // src/hooks.ts
26
43
  var arbiQueryKeys = {
27
44
  workspaces: () => ["arbi", "workspaces"],
28
45
  documents: (workspaceId) => ["arbi", "documents", workspaceId],
@@ -40,7 +57,14 @@ var arbiQueryKeys = {
40
57
  projects: () => ["arbi", "projects"],
41
58
  projectUsage: (projectExtId, monthsBack) => ["arbi", "projectUsage", projectExtId, monthsBack],
42
59
  projectInvoices: (projectExtId) => ["arbi", "projectInvoices", projectExtId],
43
- userUsageToday: () => ["arbi", "userUsageToday"]
60
+ userUsageToday: () => ["arbi", "userUsageToday"],
61
+ contacts: () => ["arbi", "contacts"],
62
+ skills: () => ["arbi", "skills"],
63
+ taskRuns: (taskId) => ["arbi", "taskRuns", taskId],
64
+ /** An agent's pinned config; `undefined` = the library default config. */
65
+ agentConfig: (agentExtId) => ["arbi", "agentConfig", agentExtId ?? "default"],
66
+ agentWorkspaces: (agentExtId) => ["arbi", "agentWorkspaces", agentExtId],
67
+ models: () => ["arbi", "models"]
44
68
  };
45
69
  function useWorkspaces(options) {
46
70
  const arbi = useArbi();
@@ -169,15 +193,20 @@ function useProjects(options) {
169
193
  }
170
194
  function useProjectUsage(projectExtId, monthsBack = 0, options) {
171
195
  const arbi = useArbi();
172
- return useQuery({
196
+ const usage = useQuery({
173
197
  queryKey: arbiQueryKeys.projectUsage(projectExtId ?? "", monthsBack),
174
198
  queryFn: () => arbi.projects.usage(projectExtId, monthsBack),
175
199
  enabled: (options?.enabled ?? true) && !!projectExtId && arbi.isLoggedIn
176
200
  });
201
+ const projects = useProjects({ enabled: !!projectExtId && (options?.enabled ?? true) });
202
+ const subscription = projects.data?.find((p) => p.external_id === projectExtId)?.subscription;
203
+ const metered = usage.data ? isUsageMetered(usage.data, subscription) : void 0;
204
+ return { ...usage, metered };
177
205
  }
178
206
  function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
179
207
  const arbi = useArbi();
180
208
  const enabled = (options?.enabled ?? true) && arbi.isLoggedIn;
209
+ const projects = useProjects({ enabled: enabled && projectExtIds.length > 0 });
181
210
  const results = useQueries({
182
211
  queries: projectExtIds.map((id) => ({
183
212
  queryKey: arbiQueryKeys.projectUsage(id, monthsBack),
@@ -185,27 +214,34 @@ function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
185
214
  enabled: enabled && !!id
186
215
  }))
187
216
  });
188
- const signature = results.map((r) => `${r.status}:${r.dataUpdatedAt ?? 0}`).join("|");
217
+ const signature = results.map((r) => `${r.status}:${r.dataUpdatedAt ?? 0}`).join("|") + `#${projects.dataUpdatedAt}`;
189
218
  return useMemo(() => {
190
219
  const dailyMap = /* @__PURE__ */ new Map();
191
220
  const modelMap = /* @__PURE__ */ new Map();
192
- let totalSpend = 0;
221
+ let totalCredits = 0;
222
+ let anyData = false;
223
+ let anyMetered = false;
193
224
  let totalTokens = 0;
194
225
  let totalRequests = 0;
195
- for (const r of results) {
226
+ results.forEach((r, i) => {
196
227
  const d = r.data;
197
- if (!d) continue;
198
- totalSpend += d.total_spend ?? 0;
228
+ if (!d) return;
229
+ anyData = true;
230
+ const subscription = projects.data?.find(
231
+ (p) => p.external_id === projectExtIds[i]
232
+ )?.subscription;
233
+ if (isUsageMetered(d, subscription)) anyMetered = true;
234
+ totalCredits += d.total_spend ?? 0;
199
235
  totalTokens += d.total_tokens ?? 0;
200
236
  totalRequests += d.total_requests ?? 0;
201
237
  for (const day of d.daily ?? []) {
202
238
  const cur = dailyMap.get(day.date) ?? {
203
239
  date: day.date,
204
- spend: 0,
240
+ credits: 0,
205
241
  totalTokens: 0,
206
242
  apiRequests: 0
207
243
  };
208
- cur.spend += day.spend ?? 0;
244
+ cur.credits += day.spend ?? 0;
209
245
  cur.totalTokens += day.total_tokens ?? 0;
210
246
  cur.apiRequests += day.api_requests ?? 0;
211
247
  dailyMap.set(day.date, cur);
@@ -213,28 +249,29 @@ function useProjectsUsage(projectExtIds, monthsBack = 0, options) {
213
249
  for (const [model, mg] of Object.entries(d.model_groups ?? {})) {
214
250
  const cur = modelMap.get(model) ?? {
215
251
  model,
216
- spend: 0,
252
+ credits: 0,
217
253
  totalTokens: 0,
218
254
  promptTokens: 0,
219
255
  completionTokens: 0,
220
256
  successfulRequests: 0
221
257
  };
222
- cur.spend += mg.spend ?? 0;
258
+ cur.credits += mg.spend ?? 0;
223
259
  cur.totalTokens += mg.total_tokens ?? 0;
224
260
  cur.promptTokens += mg.prompt_tokens ?? 0;
225
261
  cur.completionTokens += mg.completion_tokens ?? 0;
226
262
  cur.successfulRequests += mg.successful_requests ?? 0;
227
263
  modelMap.set(model, cur);
228
264
  }
229
- }
265
+ });
230
266
  return {
231
- totalSpend,
267
+ totalCredits,
232
268
  totalTokens,
233
269
  totalRequests,
234
270
  daily: [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date)),
235
271
  modelGroups: [...modelMap.values()].sort(
236
- (a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens
272
+ (a, b) => b.credits - a.credits || b.totalTokens - a.totalTokens
237
273
  ),
274
+ metered: !anyData || anyMetered,
238
275
  isLoading: results.some((r) => r.isLoading),
239
276
  isError: results.some((r) => r.isError)
240
277
  };
@@ -890,6 +927,186 @@ function useArbiShellWorkspaces(options = {}) {
890
927
  select: select ?? defaultSelect
891
928
  });
892
929
  }
930
+ function useCreateTask() {
931
+ const arbi = useArbi();
932
+ const qc = useQueryClient();
933
+ return useMutation({
934
+ mutationFn: (body) => arbi.tasks.create(body),
935
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
936
+ });
937
+ }
938
+ function useUpdateTask() {
939
+ const arbi = useArbi();
940
+ const qc = useQueryClient();
941
+ return useMutation({
942
+ mutationFn: ({ taskId, patch }) => arbi.tasks.update(taskId, patch),
943
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
944
+ });
945
+ }
946
+ function useRunTask() {
947
+ const arbi = useArbi();
948
+ const qc = useQueryClient();
949
+ return useMutation({
950
+ mutationFn: (taskId) => arbi.tasks.runWithAuth(taskId),
951
+ onSuccess: (_task, taskId) => Promise.all([
952
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() }),
953
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.taskRuns(taskId) })
954
+ ])
955
+ });
956
+ }
957
+ function useDeleteTask() {
958
+ const arbi = useArbi();
959
+ const qc = useQueryClient();
960
+ return useMutation({
961
+ mutationFn: (taskId) => arbi.tasks.delete(taskId),
962
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.tasks() })
963
+ });
964
+ }
965
+ function useTaskRuns(taskId, options) {
966
+ const arbi = useArbi();
967
+ return useQuery({
968
+ queryKey: arbiQueryKeys.taskRuns(taskId ?? ""),
969
+ queryFn: () => arbi.tasks.listRuns(taskId),
970
+ enabled: (options?.enabled ?? true) && !!taskId && arbi.isLoggedIn,
971
+ refetchInterval: (q) => q.state.data?.some((r) => r.status === "running") ? 3e3 : false
972
+ });
973
+ }
974
+ function useCreateAgent() {
975
+ const arbi = useArbi();
976
+ const qc = useQueryClient();
977
+ return useMutation({
978
+ mutationFn: (name) => arbi.agents.create(name),
979
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.agents() })
980
+ });
981
+ }
982
+ function useAgentConfig(agentExtId, options) {
983
+ const arbi = useArbi();
984
+ return useQuery({
985
+ queryKey: arbiQueryKeys.agentConfig(agentExtId),
986
+ queryFn: async () => agentExtId ? await arbi.agents.getConfig(agentExtId) : await arbi.agentConfig.get("default"),
987
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
988
+ });
989
+ }
990
+ function useAgentModels(options) {
991
+ const arbi = useArbi();
992
+ return useQuery({
993
+ queryKey: arbiQueryKeys.models(),
994
+ queryFn: async () => (await arbi.agentConfig.getModels()).models.map((m) => m.model_name),
995
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
996
+ });
997
+ }
998
+ function useSaveAgentConfig() {
999
+ const arbi = useArbi();
1000
+ const qc = useQueryClient();
1001
+ return useMutation({
1002
+ mutationFn: async ({ agentExtId, title, sections }) => {
1003
+ const pinned = await arbi.agents.getConfig(agentExtId);
1004
+ const base = pinned ?? await arbi.agentConfig.get("default");
1005
+ const merged = structuredClone(base);
1006
+ for (const [section, params] of Object.entries(sections)) {
1007
+ merged[section] = { ...merged[section] ?? {}, ...params };
1008
+ }
1009
+ return arbi.agentConfig.save({
1010
+ ...merged,
1011
+ agent_ext_id: agentExtId,
1012
+ title: title ?? `agent-${agentExtId}`
1013
+ });
1014
+ },
1015
+ onSuccess: (_saved, { agentExtId }) => Promise.all([
1016
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.configs() }),
1017
+ qc.invalidateQueries({ queryKey: arbiQueryKeys.agentConfig(agentExtId) })
1018
+ ])
1019
+ });
1020
+ }
1021
+ function useAgentWorkspaces(agentExtId, options) {
1022
+ const arbi = useArbi();
1023
+ return useQuery({
1024
+ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId ?? ""),
1025
+ queryFn: () => arbi.agents.listWorkspaces(agentExtId),
1026
+ enabled: (options?.enabled ?? true) && !!agentExtId && arbi.isLoggedIn
1027
+ });
1028
+ }
1029
+ function useGrantAgentAccess() {
1030
+ const arbi = useArbi();
1031
+ const qc = useQueryClient();
1032
+ return useMutation({
1033
+ mutationFn: async ({
1034
+ agentExtId,
1035
+ workspaceId,
1036
+ mode = { kind: "standing" }
1037
+ }) => {
1038
+ if (mode.kind === "temporary") {
1039
+ return arbi.agents.grantTemporaryAccess(agentExtId, workspaceId, mode.ttlSeconds);
1040
+ }
1041
+ const agent = (await arbi.agents.list()).find((a) => a.external_id === agentExtId);
1042
+ if (!agent?.email) throw new Error("The agent's address is unknown.");
1043
+ return arbi.workspaces.grantAccess({ workspaceId, emails: [agent.email] });
1044
+ },
1045
+ onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
1046
+ });
1047
+ }
1048
+ function useRevokeAgentAccess() {
1049
+ const arbi = useArbi();
1050
+ const qc = useQueryClient();
1051
+ return useMutation({
1052
+ mutationFn: async ({
1053
+ agentExtId,
1054
+ workspaceId
1055
+ }) => {
1056
+ const previous = arbi.hasWorkspace ? arbi.getWorkspaceId() : null;
1057
+ await arbi.selectWorkspace(workspaceId);
1058
+ try {
1059
+ await arbi.workspaces.removeUsers([agentExtId]);
1060
+ } finally {
1061
+ if (previous && previous !== workspaceId) await arbi.selectWorkspace(previous);
1062
+ }
1063
+ },
1064
+ onSuccess: (_res, { agentExtId }) => qc.invalidateQueries({ queryKey: arbiQueryKeys.agentWorkspaces(agentExtId) })
1065
+ });
1066
+ }
1067
+ function useSkills(options) {
1068
+ const arbi = useArbi();
1069
+ return useQuery({
1070
+ queryKey: [...arbiQueryKeys.skills(), options?.includeHidden ?? false],
1071
+ queryFn: () => arbi.assistant.listSkills({ includeHidden: options?.includeHidden }),
1072
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn,
1073
+ staleTime: 3e4
1074
+ });
1075
+ }
1076
+ function useContacts(options) {
1077
+ const arbi = useArbi();
1078
+ return useQuery({
1079
+ queryKey: arbiQueryKeys.contacts(),
1080
+ queryFn: () => arbi.contacts.list(),
1081
+ enabled: (options?.enabled ?? true) && arbi.isLoggedIn
1082
+ });
1083
+ }
1084
+ function usePendingContacts(options) {
1085
+ const contacts = useContacts(options);
1086
+ const pending = useMemo(
1087
+ () => (contacts.data ?? []).filter(
1088
+ (c) => c.status === "invitation_pending" || c.status === "invitation_expired"
1089
+ ),
1090
+ [contacts.data]
1091
+ );
1092
+ return { ...contacts, pending };
1093
+ }
1094
+ function useAddContacts() {
1095
+ const arbi = useArbi();
1096
+ const qc = useQueryClient();
1097
+ return useMutation({
1098
+ mutationFn: (emails) => arbi.contacts.add(emails),
1099
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.contacts() })
1100
+ });
1101
+ }
1102
+ function useRemoveContacts() {
1103
+ const arbi = useArbi();
1104
+ const qc = useQueryClient();
1105
+ return useMutation({
1106
+ mutationFn: (contactIds) => arbi.contacts.remove(contactIds),
1107
+ onSuccess: () => qc.invalidateQueries({ queryKey: arbiQueryKeys.contacts() })
1108
+ });
1109
+ }
893
1110
  function useWorkspaceDocs(workspaceId) {
894
1111
  const { data: docs = [], isLoading, isError } = useDocuments(workspaceId);
895
1112
  const docIds = useMemo(
@@ -3065,7 +3282,6 @@ function isAgentUser(user) {
3065
3282
  return user.external_id.startsWith("agt-");
3066
3283
  }
3067
3284
  var DM_QUERY_KEY = ["arbi", "directMessages"];
3068
- var CONTACTS_QUERY_KEY = ["arbi", "contacts"];
3069
3285
  function mergeContacts(threads, contacts) {
3070
3286
  const byPeer = /* @__PURE__ */ new Map();
3071
3287
  for (const t of threads) byPeer.set(t.peer.external_id, t);
@@ -3131,7 +3347,7 @@ function useDirectMessages(options) {
3131
3347
  enabled
3132
3348
  });
3133
3349
  const contactsQuery = useQuery({
3134
- queryKey: CONTACTS_QUERY_KEY,
3350
+ queryKey: arbiQueryKeys.contacts(),
3135
3351
  queryFn: () => arbi.contacts.list(),
3136
3352
  enabled
3137
3353
  });
@@ -3216,6 +3432,7 @@ function ConversationList({
3216
3432
  classNames,
3217
3433
  emptyState,
3218
3434
  header,
3435
+ lead,
3219
3436
  className
3220
3437
  }) {
3221
3438
  return /* @__PURE__ */ jsxs(
@@ -3235,6 +3452,7 @@ function ConversationList({
3235
3452
  conversations.length === 1 ? "colleague" : "colleagues"
3236
3453
  ] })
3237
3454
  ] }) : header,
3455
+ lead,
3238
3456
  /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: conversations.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-xs text-muted-foreground", children: emptyState ?? "No conversations yet." }) : /* @__PURE__ */ jsx("ul", { className: "w-full", children: conversations.map((c, i) => {
3239
3457
  const { peer, lastMessage, unreadCount } = c;
3240
3458
  const newSection = c.sectionLabel && c.sectionLabel !== conversations[i - 1]?.sectionLabel;
@@ -3521,14 +3739,117 @@ function DmComposer({
3521
3739
  error && /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-destructive", "data-testid": "arbi-dm-error", children: error })
3522
3740
  ] });
3523
3741
  }
3742
+ var DEFAULT_START_BY_EMAIL_LABELS = {
3743
+ email: "Email address",
3744
+ emailPlaceholder: "name@example.com",
3745
+ message: "Message",
3746
+ messagePlaceholder: "Write the first message\u2026",
3747
+ send: "Send",
3748
+ sending: "Sending\u2026",
3749
+ invalidEmail: "Enter a valid email address."
3750
+ };
3751
+ var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3752
+ function StartByEmail({
3753
+ onStart,
3754
+ onStarted,
3755
+ classNames,
3756
+ labels,
3757
+ className
3758
+ }) {
3759
+ const l = { ...DEFAULT_START_BY_EMAIL_LABELS, ...labels };
3760
+ const uid = useId();
3761
+ const [email, setEmail] = useState("");
3762
+ const [text, setText] = useState("");
3763
+ const [busy, setBusy] = useState(false);
3764
+ const [error, setError] = useState(null);
3765
+ const address = email.trim().toLowerCase();
3766
+ const valid = EMAIL.test(address);
3767
+ const submit = async () => {
3768
+ if (!valid || !text.trim() || busy) return;
3769
+ setBusy(true);
3770
+ setError(null);
3771
+ try {
3772
+ const result = await onStart(address, text.trim());
3773
+ setEmail("");
3774
+ setText("");
3775
+ onStarted?.(result);
3776
+ } catch (e) {
3777
+ setError(e instanceof Error ? e.message : String(e));
3778
+ } finally {
3779
+ setBusy(false);
3780
+ }
3781
+ };
3782
+ return /* @__PURE__ */ jsxs(
3783
+ "form",
3784
+ {
3785
+ "data-testid": "arbi-dm-start",
3786
+ className: cn("flex flex-col gap-2 border-b border-border px-4 py-3", className),
3787
+ onSubmit: (e) => {
3788
+ e.preventDefault();
3789
+ void submit();
3790
+ },
3791
+ children: [
3792
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-email`, children: l.email }),
3793
+ /* @__PURE__ */ jsx(
3794
+ Input,
3795
+ {
3796
+ id: `${uid}-email`,
3797
+ type: "email",
3798
+ value: email,
3799
+ onChange: (e) => setEmail(e.target.value),
3800
+ placeholder: l.emailPlaceholder,
3801
+ "aria-invalid": email !== "" && !valid,
3802
+ "aria-describedby": email !== "" && !valid ? `${uid}-bad` : void 0,
3803
+ disabled: busy,
3804
+ "data-testid": "arbi-dm-start-email"
3805
+ }
3806
+ ),
3807
+ email !== "" && !valid && /* @__PURE__ */ jsx("p", { id: `${uid}-bad`, className: "text-xs text-destructive", children: l.invalidEmail }),
3808
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-text`, children: l.message }),
3809
+ /* @__PURE__ */ jsx(
3810
+ Textarea,
3811
+ {
3812
+ id: `${uid}-text`,
3813
+ value: text,
3814
+ onChange: (e) => setText(e.target.value),
3815
+ placeholder: l.messagePlaceholder,
3816
+ rows: 2,
3817
+ disabled: busy,
3818
+ "data-testid": "arbi-dm-start-message"
3819
+ }
3820
+ ),
3821
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-destructive", "data-testid": "arbi-dm-start-error", children: error }),
3822
+ /* @__PURE__ */ jsxs(
3823
+ Button,
3824
+ {
3825
+ type: "submit",
3826
+ size: "sm",
3827
+ disabled: !valid || !text.trim() || busy,
3828
+ className: cn("self-end", classNames?.sendButton),
3829
+ "data-testid": "arbi-dm-start-send",
3830
+ children: [
3831
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }) : /* @__PURE__ */ jsx(Send, { className: "size-4", "aria-hidden": true }),
3832
+ busy ? l.sending : l.send
3833
+ ]
3834
+ }
3835
+ )
3836
+ ]
3837
+ }
3838
+ );
3839
+ }
3524
3840
  function DirectMessages({
3525
3841
  className,
3526
3842
  classNames,
3527
3843
  emptyThread: emptyThread2,
3528
- emptyList
3844
+ emptyList,
3845
+ listHeader,
3846
+ listLead,
3847
+ startByEmail,
3848
+ hideOwnContact
3529
3849
  }) {
3530
3850
  const {
3531
- conversations,
3851
+ conversations: allConversations,
3852
+ myExtId,
3532
3853
  isLoading,
3533
3854
  isError,
3534
3855
  error,
@@ -3538,6 +3859,14 @@ function DirectMessages({
3538
3859
  markRead
3539
3860
  } = useDirectMessages();
3540
3861
  const [selectedPeerId, setSelectedPeerId] = useState(null);
3862
+ const conversations = useMemo(
3863
+ () => hideOwnContact && myExtId ? allConversations.filter((c) => c.peer.external_id !== myExtId) : allConversations,
3864
+ [allConversations, hideOwnContact, myExtId]
3865
+ );
3866
+ const openStarted = (created) => {
3867
+ const peer = Array.isArray(created) ? created[0]?.recipient?.external_id : void 0;
3868
+ if (peer) setSelectedPeerId(peer);
3869
+ };
3541
3870
  const loadError = isError ? error instanceof Error ? error.message : "Couldn\u2019t load messages." : null;
3542
3871
  const selected = useMemo(
3543
3872
  () => conversations.find((c) => c.peer.external_id === selectedPeerId) ?? null,
@@ -3581,7 +3910,20 @@ function DirectMessages({
3581
3910
  selectedPeerId: selected?.peer.external_id ?? null,
3582
3911
  onSelect: setSelectedPeerId,
3583
3912
  classNames,
3584
- emptyState: isLoading ? "Loading messages\u2026" : emptyList
3913
+ emptyState: isLoading ? "Loading messages\u2026" : emptyList,
3914
+ header: listHeader,
3915
+ lead: /* @__PURE__ */ jsxs(Fragment, { children: [
3916
+ listLead,
3917
+ startByEmail && /* @__PURE__ */ jsx(
3918
+ StartByEmail,
3919
+ {
3920
+ onStart: sendMessageTo,
3921
+ onStarted: openStarted,
3922
+ classNames,
3923
+ labels: typeof startByEmail === "object" ? startByEmail.labels : void 0
3924
+ }
3925
+ )
3926
+ ] })
3585
3927
  }
3586
3928
  ),
3587
3929
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", children: [
@@ -4990,6 +5332,6 @@ function hasVideoTrack(blob) {
4990
5332
  });
4991
5333
  }
4992
5334
 
4993
- export { ActivityFeed, AiActionCard, AiAnswer, AppleIcon, ArbiBotIcon, ArbiIcon, ArbiWebSocketProvider, ArtifactCanvas, ChatBubble, CitationRenderer, ClaudeIcon, CommandPalette, CommandPaletteProvider, ConversationList, DEFAULT_ASPECT_OPTIONS, DateSeparator, DirectMessages, DmAvatar, DmComposer, DocIcon, DocQaPanel, DocSearch, DocumentCardMenu, DocumentGallery, DocumentReview, DocumentTable, DraftTitleControls, FileIcon, FireworksIcon, GeminiIcon, GitHubIcon, GoogleIcon, HtmlIcon, ImageCropModal, ImageCropper, LinkedInIcon, MessageArtifactCard, MessageBubble, MessageThread, OpenAIIcon, PhotoCapture, ProviderIcon, RUN_WINDOW_MS, SemanticSearch, TxtIcon, TypingIndicator, VideoCapture, VoiceNoteRecorder, XlsIcon, annotateThread, arbiQueryKeys, captureFrameAsBlob, cropImage, dayKey, describeActivity, getNotificationTypeMeta, getWorkspaceActionText, getWorkspaceKind, getWorkspaceSentPhrase, hasVideoTrack, isWorkspaceNotificationType, parseActivityContent, pickRecentWorkspaces, redlineStats, resolveActiveWorkspace, textToArtifact, titleFromQuestion, toRedlineMarkdown, toShellWorkspace, useActivity, useAgents, useAiTask, useArbiChat, useArbiConnected, useArbiDocumentSync, useArbiShellWorkspaces, useArbiUiStore, useArbiWebSocket, useArbiWsContext, useArbiWsMessage, useArbiWsStatus, useArbiWsStore, useArbiWsTask, useArbiWsTasks, useAssistantQuery, useCameraStream, useCommandPalette, useConfigs, useConversations, useDeleteAgents, useDirectMessages, useDocuments, useFacts, useHealth, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSemanticSearch, useSessions, useShellWorkspaces, useTags, useTasks, useThumbnails, useUserUsageToday, useVideoRecorder, useWorkspaceDocs, useWorkspaces, userDisplayName, userInitials };
5335
+ export { ActivityFeed, AiActionCard, AiAnswer, AppleIcon, ArbiBotIcon, ArbiIcon, ArbiWebSocketProvider, ArtifactCanvas, ChatBubble, CitationRenderer, ClaudeIcon, CommandPalette, CommandPaletteProvider, ConversationList, DEFAULT_ASPECT_OPTIONS, DEFAULT_START_BY_EMAIL_LABELS, DEFAULT_USD_PER_CREDIT, DateSeparator, DirectMessages, DmAvatar, DmComposer, DocIcon, DocQaPanel, DocSearch, DocumentCardMenu, DocumentGallery, DocumentReview, DocumentTable, DraftTitleControls, FileIcon, FireworksIcon, GeminiIcon, GitHubIcon, GoogleIcon, HtmlIcon, ImageCropModal, ImageCropper, LinkedInIcon, MessageArtifactCard, MessageBubble, MessageThread, OpenAIIcon, PhotoCapture, ProviderIcon, RUN_WINDOW_MS, SemanticSearch, StartByEmail, TxtIcon, TypingIndicator, VideoCapture, VoiceNoteRecorder, XlsIcon, annotateThread, arbiQueryKeys, captureFrameAsBlob, creditsToUsd, cropImage, dayKey, describeActivity, formatCredits, getNotificationTypeMeta, getWorkspaceActionText, getWorkspaceKind, getWorkspaceSentPhrase, hasVideoTrack, isUsageMetered, isWorkspaceNotificationType, parseActivityContent, pickRecentWorkspaces, redlineStats, resolveActiveWorkspace, textToArtifact, titleFromQuestion, toRedlineMarkdown, toShellWorkspace, useActivity, useAddContacts, useAgentConfig, useAgentModels, useAgentWorkspaces, useAgents, useAiTask, useArbiChat, useArbiConnected, useArbiDocumentSync, useArbiShellWorkspaces, useArbiUiStore, useArbiWebSocket, useArbiWsContext, useArbiWsMessage, useArbiWsStatus, useArbiWsStore, useArbiWsTask, useArbiWsTasks, useAssistantQuery, useCameraStream, useCommandPalette, useConfigs, useContacts, useConversations, useCreateAgent, useCreateTask, useDeleteAgents, useDeleteTask, useDirectMessages, useDocuments, useFacts, useGrantAgentAccess, useHealth, useParsedDocument, usePendingContacts, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useRemoveContacts, useRevokeAgentAccess, useRunTask, useSaveAgentConfig, useSemanticSearch, useSessions, useShellWorkspaces, useSkills, useTags, useTaskRuns, useTasks, useThumbnails, useUpdateTask, useUserUsageToday, useVideoRecorder, useWorkspaceDocs, useWorkspaces, userDisplayName, userInitials };
4994
5336
  //# sourceMappingURL=index.js.map
4995
5337
  //# sourceMappingURL=index.js.map