@taskforcehq/taskforce 0.3.314 → 0.3.316

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.
Files changed (36) hide show
  1. package/dist/components/features/AnnotatedAttachmentWorkspace.js +51 -11
  2. package/dist/core/PlanEntitlementService.js +6 -1
  3. package/dist/core/Taskforce.d.ts +2 -0
  4. package/dist/core/Taskforce.js +16 -2
  5. package/dist/hooks/useTaskforce.js +13 -6
  6. package/dist/mcp/canonicalAssetHelpers.js +33 -19
  7. package/dist/mcp/runtime.js +39 -9
  8. package/dist/mcp/taskAttachmentHelpers.js +29 -7
  9. package/dist/mcp/toolCatalog.d.ts +7 -0
  10. package/dist/server/index.js +1 -1
  11. package/dist/server/routes/documents.js +2 -1
  12. package/dist/server/routes.js +10 -6
  13. package/dist/storage/documentIntegrity.js +1 -6
  14. package/dist/storage/documentPurge.js +11 -12
  15. package/dist/sync/workspaceRepair.js +11 -9
  16. package/dist/ui/.well-known/mcp-registry-auth +1 -0
  17. package/dist/ui/assets/{AgentsModule-jZnjzh-I.js → AgentsModule-CNBWCIXk.js} +1 -1
  18. package/dist/ui/assets/AnnotatedAttachmentWorkspace-9qxB0e6A.js +3 -0
  19. package/dist/ui/assets/{ContextAttachmentManager-OnjnW5fC.js → ContextAttachmentManager-CRyuFYlg.js} +1 -1
  20. package/dist/ui/assets/{DocumentWorkspace-Btxo9quS.js → DocumentWorkspace-Dx7wb9NF.js} +1 -1
  21. package/dist/ui/assets/{EntityActivityTimeline-C8BjU7yO.js → EntityActivityTimeline-Cmal2OrJ.js} +1 -1
  22. package/dist/ui/assets/{InitiativesModule-DYBB7WD-.js → InitiativesModule-wzbYPQQL.js} +1 -1
  23. package/dist/ui/assets/{PlansPage-p0gvF4qR.js → PlansPage-BAnSfbTf.js} +1 -1
  24. package/dist/ui/assets/{TaskContextUpload-EGeOdrPm.js → TaskContextUpload-DENyMyUk.js} +1 -1
  25. package/dist/ui/assets/{TaskSettings-CJpF1CMF.js → TaskSettings-DASuVwpY.js} +1 -1
  26. package/dist/ui/assets/{WorkflowsModule-IQOmxPXX.js → WorkflowsModule-DJMEA_yt.js} +1 -1
  27. package/dist/ui/assets/documentReferences-BhNx80zO.js +1 -0
  28. package/dist/ui/assets/{index-DUt7ifSO.js → index-CWg2olz9.js} +5 -5
  29. package/dist/ui/index.html +1 -1
  30. package/dist/utils/pathContainment.d.ts +7 -0
  31. package/dist/utils/pathContainment.js +53 -0
  32. package/dist/utils/pathSafety.d.ts +6 -0
  33. package/dist/utils/pathSafety.js +73 -0
  34. package/package.json +3 -1
  35. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CGFrFF0m.js +0 -3
  36. package/dist/ui/assets/documentReferences-DXW5aT08.js +0 -1
@@ -25,6 +25,7 @@
25
25
  import * as fs from 'fs';
26
26
  import * as path from 'path';
27
27
  import { normalizeTaskDataRelativePath } from '../../storage/objectStorageClient.js';
28
+ import { resolveExistingStorageKeyFilePathInsideRoot } from '../../utils/pathSafety.js';
28
29
  import { shouldUseProvisionalTaskReferences } from '../../utils/taskReferences.js';
29
30
  import { purgeExpiredDocuments } from '../../storage/documentPurge.js';
30
31
  import { parsePlan } from '../../utils/planParser.js';
@@ -427,7 +428,7 @@ export function registerDocumentRoutes(deps) {
427
428
  return;
428
429
  }
429
430
  const filePath = normalizedRelative
430
- ? ensureWithinDir(path.join(basePath, normalizedRelative), basePath)
431
+ ? resolveExistingStorageKeyFilePathInsideRoot(basePath, normalizedRelative)
431
432
  : null;
432
433
  const ext = normalizedRelative ? path.extname(normalizedRelative).toLowerCase() : '';
433
434
  const resolvedDownloadNameBase = sanitizeDownloadFilename(requestedFilename
@@ -23,6 +23,7 @@ import { getDocumentReferenceLabel } from '../utils/documentReferences.js';
23
23
  import { getImageReferenceLabel } from '../utils/imageReferences.js';
24
24
  import { inferCanonicalAssetKind, isValidCanonicalDocumentAsset } from '../utils/canonicalAssetKind.js';
25
25
  import { isReservedWorkspaceId } from '../utils/workspaceIdentity.js';
26
+ import { normalizeRelativeStorageKey, resolveExistingStorageKeyFilePathInsideRoot } from '../utils/pathSafety.js';
26
27
  import { resolveVirusScanConfigFromEnv, scanBufferForThreats } from '../security/virusScan.js';
27
28
  import { registerSyncRoutes } from './routes/sync.js';
28
29
  import { registerBillingRoutes } from './routes/billing.js';
@@ -788,11 +789,14 @@ export function createRoutes(core, context = {}) {
788
789
  const resolvedObjectStorage = resolveEffectiveObjectStorageConfig();
789
790
  const objectStorageClient = getObjectStorageClient();
790
791
  const { basePath } = core.getPaths();
791
- const ext = path.extname(params.asset.storageKey).toLowerCase();
792
+ const storageKey = normalizeRelativeStorageKey(params.asset.storageKey);
793
+ if (!storageKey)
794
+ return { statusCode: 404 };
795
+ const ext = path.extname(storageKey).toLowerCase();
792
796
  const resolvedDownloadNameBase = sanitizeDownloadFilename(String(params.requestedFilename || '').trim()
793
797
  || params.asset.logicalName
794
798
  || params.asset.originalFilename
795
- || path.basename(params.asset.storageKey, ext)
799
+ || path.basename(storageKey, ext)
796
800
  || 'download');
797
801
  const resolvedDownloadName = ext && !resolvedDownloadNameBase.toLowerCase().endsWith(ext)
798
802
  ? `${resolvedDownloadNameBase}${ext}`
@@ -805,7 +809,7 @@ export function createRoutes(core, context = {}) {
805
809
  return { statusCode: 404 };
806
810
  }
807
811
  if (!params.forceDownload) {
808
- const object = await objectStorageClient.getObject(params.asset.storageKey);
812
+ const object = await objectStorageClient.getObject(storageKey);
809
813
  if (!object) {
810
814
  return { statusCode: 404 };
811
815
  }
@@ -821,15 +825,15 @@ export function createRoutes(core, context = {}) {
821
825
  return {
822
826
  statusCode: 302,
823
827
  headers: {
824
- Location: objectStorageClient.createSignedGetUrl(params.asset.storageKey, resolvedObjectStorage.r2.signedDownloadTtlSeconds, { responseContentDisposition: contentDisposition })
828
+ Location: objectStorageClient.createSignedGetUrl(storageKey, resolvedObjectStorage.r2.signedDownloadTtlSeconds, { responseContentDisposition: contentDisposition })
825
829
  }
826
830
  };
827
831
  };
828
832
  if (params.asset.storageProvider === 'r2' && objectStorageClient && resolvedObjectStorage.provider === 'r2' && resolvedObjectStorage.r2) {
829
833
  return readFromObjectStorage();
830
834
  }
831
- const filePath = ensureWithinDir(path.join(basePath, params.asset.storageKey), basePath);
832
- if (filePath && fs.existsSync(filePath)) {
835
+ const filePath = resolveExistingStorageKeyFilePathInsideRoot(basePath, storageKey);
836
+ if (filePath) {
833
837
  return {
834
838
  statusCode: 200,
835
839
  headers: {
@@ -2,12 +2,7 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { DocumentRegistry, createDocumentHash } from './documentRegistry.js';
4
4
  import { ObjectStorageClient } from './objectStorageClient.js';
5
- function isPathInsideRoot(candidatePath, rootDir) {
6
- const resolved = path.resolve(candidatePath);
7
- const root = path.resolve(rootDir);
8
- const relative = path.relative(root, resolved);
9
- return !relative.startsWith('..') && !path.isAbsolute(relative);
10
- }
5
+ import { isPathInsideRoot } from '../utils/pathSafety.js';
11
6
  function inc(byCode, code) {
12
7
  byCode[code] = (byCode[code] || 0) + 1;
13
8
  }
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'fs';
2
- import * as path from 'path';
3
2
  import { DocumentRegistry } from './documentRegistry.js';
4
3
  import { ObjectStorageClient } from './objectStorageClient.js';
4
+ import { normalizeRelativeStorageKey, resolveStorageKeyPathInsideRoot } from '../utils/pathSafety.js';
5
5
  export async function purgeExpiredDocuments(params) {
6
6
  const registry = new DocumentRegistry(params.basePath);
7
7
  const now = params.now || new Date();
@@ -32,33 +32,32 @@ export async function purgeExpiredDocuments(params) {
32
32
  for (const entry of candidates) {
33
33
  try {
34
34
  const storageKey = 'storageKey' in entry ? entry.storageKey : entry.path;
35
+ const normalizedStorageKey = normalizeRelativeStorageKey(storageKey);
36
+ if (!normalizedStorageKey)
37
+ throw new Error('Invalid storage key');
35
38
  const workspaceId = entry?.workspaceId
36
39
  ? String(entry.workspaceId)
37
40
  : (() => {
38
- const normalized = String(storageKey || '').trim().replace(/\\/g, '/');
39
- const match = normalized.match(/^workspaces\/([^/]+)\//);
41
+ const match = normalizedStorageKey.match(/^workspaces\/([^/]+)\//);
40
42
  return match?.[1] ? match[1] : 'default';
41
43
  })();
42
44
  if (objectStorageClient) {
43
- await objectStorageClient.deleteObject(storageKey);
45
+ await objectStorageClient.deleteObject(normalizedStorageKey);
44
46
  }
45
47
  else {
46
- const filePath = path.resolve(params.basePath, storageKey);
47
- const storageRoot = path.resolve(params.basePath);
48
- const relative = path.relative(storageRoot, filePath);
49
- const insideStorageRoot = !relative.startsWith('..') && !path.isAbsolute(relative);
50
- if (insideStorageRoot && fs.existsSync(filePath)) {
48
+ const filePath = resolveStorageKeyPathInsideRoot(params.basePath, normalizedStorageKey);
49
+ if (filePath && fs.existsSync(filePath)) {
51
50
  fs.unlinkSync(filePath);
52
51
  }
53
52
  }
54
53
  if (params.assetStore) {
55
- params.assetStore.remove(storageKey, workspaceId);
54
+ params.assetStore.remove(normalizedStorageKey, workspaceId);
56
55
  }
57
56
  else {
58
- registry.remove(storageKey);
57
+ registry.remove(normalizedStorageKey);
59
58
  }
60
59
  if (params.workspaceAssetStore) {
61
- const canonical = params.workspaceAssetStore.getByStorageKey(storageKey, workspaceId);
60
+ const canonical = params.workspaceAssetStore.getByStorageKey(normalizedStorageKey, workspaceId);
62
61
  if (canonical) {
63
62
  params.workspaceAssetStore.remove(canonical.assetId, workspaceId);
64
63
  }
@@ -1,16 +1,14 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { createHash, randomUUID } from 'crypto';
4
+ import { resolveStorageKeyPathInsideRoot } from '../utils/pathSafety.js';
4
5
  import { compareWorkspaceMutationOrdering } from './workspaceSyncSurface.js';
6
+ import { relativePathWithinRoot } from '../utils/pathContainment.js';
5
7
  function normalizeWorkspaceId(raw) {
6
8
  return String(raw || '').trim() || 'default';
7
9
  }
8
10
  function toRelativeWorkspacePath(basePath, candidatePath) {
9
- const root = path.resolve(basePath);
10
- const absolute = path.resolve(candidatePath);
11
- if (absolute !== root && !absolute.startsWith(`${root}${path.sep}`))
12
- return null;
13
- return path.relative(root, absolute).split(path.sep).join('/');
11
+ return relativePathWithinRoot(basePath, candidatePath);
14
12
  }
15
13
  function collectFiles(dirPath, predicate) {
16
14
  if (!fs.existsSync(dirPath))
@@ -106,8 +104,8 @@ function buildManifestEntriesFromMetadata(workspaceId, store, basePath) {
106
104
  const assetDeletes = [];
107
105
  const metadataEntries = store.listAllByWorkspace(workspaceId, { includeDeleted: true });
108
106
  for (const entry of metadataEntries) {
109
- const localPath = path.resolve(basePath, entry.storageKey);
110
- const existsLocally = entry.storageProvider === 'local' ? fs.existsSync(localPath) : true;
107
+ const localPath = resolveStorageKeyPathInsideRoot(basePath, entry.storageKey);
108
+ const existsLocally = entry.storageProvider === 'local' && localPath ? fs.existsSync(localPath) : entry.storageProvider !== 'local';
111
109
  if (entry.kind === 'document') {
112
110
  if (entry.deletedAt) {
113
111
  documentDeletes.push({
@@ -373,7 +371,9 @@ export function scanWorkspaceRepair(input) {
373
371
  };
374
372
  }
375
373
  function upsertRecoveredDocumentMetadata(input) {
376
- const absolutePath = path.resolve(input.basePath, input.relativePath);
374
+ const absolutePath = resolveStorageKeyPathInsideRoot(input.basePath, input.relativePath);
375
+ if (!absolutePath)
376
+ return false;
377
377
  const fingerprint = readFileFingerprint(absolutePath);
378
378
  if (!fingerprint)
379
379
  return false;
@@ -405,7 +405,9 @@ function upsertRecoveredDocumentMetadata(input) {
405
405
  return true;
406
406
  }
407
407
  function upsertRecoveredBinaryAssetMetadata(input) {
408
- const absolutePath = path.resolve(input.basePath, input.relativePath);
408
+ const absolutePath = resolveStorageKeyPathInsideRoot(input.basePath, input.relativePath);
409
+ if (!absolutePath)
410
+ return false;
409
411
  const fingerprint = readFileFingerprint(absolutePath);
410
412
  if (!fingerprint)
411
413
  return false;
@@ -0,0 +1 @@
1
+ v=MCPv1; k=ed25519; p=K2/btkflrcbEz+CtHds6IsfrDJx6EiEjpMi/hzE97uI=
@@ -1 +1 @@
1
- import{r as c,j as e}from"./vendor-react-CKJs5o3c.js";import{s as Ue,p as _,u as ie,v as De,w as se,x as le,y as we,t as r,E as Te,z as Me,B as ke,C as fe,F as b,G as oe}from"./index-DUt7ifSO.js";import{a0 as Re,x as $e,h as Ee,B as ne,m as C,b as Le,$ as Be,a5 as Ge}from"./vendor-icons-CLnehDTw.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function Fe(l){return typeof l.avatarUrl=="string"&&l.avatarUrl.trim().length>0}function K(l){return l?fe(l.avatarUrl,l.avatarRevision,l.avatarUpdatedAt):""}function ze(l){return l?fe(l.avatarSourceUrl,l.avatarRevision,l.avatarUpdatedAt):""}function He(l){return l.find(Fe)||l[0]}function ce(l){return`${l.seatScope||"unknown"}:${l.name.trim().toLowerCase()}`}function Oe(l){const m=Math.max(0,l-1);return`${m} duplicate${m===1?"":"s"}`}function de(l){return!l||typeof l!="object"?null:{used:Math.max(0,Number(l.used||0)),limit:l.limit===null||l.limit===void 0?null:Math.max(0,Number(l.limit||0)),remaining:l.remaining===null||l.remaining===void 0?null:Math.max(0,Number(l.remaining||0))}}function pe(l,m){const U=m?.variant==="inline"?`${r.aiProfileSeatScopeIcon} ${r.aiProfileSeatScopeIconInline}`:m?.variant==="detail"?`${r.aiProfileSeatScopeIcon} ${r.aiProfileSeatScopeIconDetail}`:r.aiProfileSeatScopeIcon;return l==="cloud_metered"?e.jsx("span",{className:`${U} ${r.aiProfileSeatScopeIconCloud}`,title:"Cloud MCP","aria-label":"Cloud MCP",children:e.jsx(Be,{size:20})}):l==="local_unmetered"?e.jsx("span",{className:`${U} ${r.aiProfileSeatScopeIconLocal}`,title:"Local MCP","aria-label":"Local MCP",children:e.jsx(Ge,{size:20})}):null}function qe({workspaceId:l,cloudAuthConfigured:m=!1,authSessionResolved:U=!1,isAuthenticated:ue=!1,cloudAiProfileSeatUsage:me=null,agentTrayOpen:E=!1,onCloseAgentTray:he,mcpSettingsNode:V=null}){const[S,L]=c.useState([]),[ve,ge]=c.useState(null),[B,J]=c.useState(!1),[h,D]=c.useState(null),[w,v]=c.useState(null),[T,x]=c.useState(null),[W,G]=c.useState(null),[ye,Y]=c.useState(!1),[P,j]=c.useState(null),[q,Q]=c.useState(null),[X,M]=c.useState(!1),[Pe,g]=c.useState(null),k=c.useCallback(async()=>{if(l){J(!0);try{const a=`/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(l)}&kind=agent`,t=await fetch(a,{credentials:"include"}),i=t.ok?await t.json().catch(()=>({})):{},o=de(i?.aiProfileSeatUsage),d=Array.isArray(i?.assignees)?i.assignees.filter(s=>s.kind==="agent").map(s=>({id:String(s.value||""),name:String(s.label||s.value||"Unknown Agent"),username:String(s.username||s.value||""),icon:String(s.icon||"Bot"),color:String(s.color||"#6B7280"),avatarUrl:typeof s.avatarUrl=="string"?s.avatarUrl:null,avatarSourceUrl:typeof s.avatarSourceUrl=="string"?s.avatarSourceUrl:null,avatarRevision:Number.isFinite(Number(s.avatarRevision))?Math.max(0,Math.floor(Number(s.avatarRevision))):0,avatarUpdatedAt:typeof s.avatarUpdatedAt=="string"?s.avatarUpdatedAt:null,kind:String(s.kind||"agent"),description:typeof s.description=="string"?s.description:null,role:typeof s.role=="string"?s.role:null,provider:typeof s.provider=="string"?s.provider:null,model:typeof s.model=="string"?s.model:null,surfaceType:_(s.surfaceType),seatScope:Ue(s.seatScope),archivedAt:typeof s.archivedAt=="string"?s.archivedAt:null,createdAt:String(s.createdAt||""),updatedAt:String(s.updatedAt||s.createdAt||""),lastActiveAt:typeof s.lastActiveAt=="string"?s.lastActiveAt:null})):[];L(d),ge(o),x(s=>s&&!d.some(p=>p.id===s.profileId)?null:s),G(s=>s&&!d.some(p=>p.id===s.profileId)?null:s)}finally{J(!1)}}},[l]);c.useEffect(()=>{k()},[k]);const R=c.useMemo(()=>{const a=new Map;for(const t of S){const i=ie(t.surfaceType);a.has(i)||a.set(i,new Map);const o=a.get(i),d=ce(t);o.has(d)||o.set(d,[]),o.get(d).push(t)}return De.map(t=>({section:t,label:se(t),groups:Array.from(a.get(t)?.entries()||[]).map(([i,o])=>({groupId:`${t}:${i}`,section:t,sectionLabel:se(t),profiles:o,primaryProfile:He(o)}))})).filter(t=>t.groups.length>0)},[S]),y=c.useMemo(()=>R.flatMap(a=>a.groups),[R]),n=c.useMemo(()=>y.find(a=>a.groupId===P)||y[0]||null,[y,P]),u=c.useMemo(()=>S.find(a=>a.id===q)||null,[S,q]);c.useEffect(()=>{if(!y.length){P!==null&&j(null);return}(!P||!y.some(a=>a.groupId===P))&&j(y[0].groupId)},[y,P]);const Ae=async(a,t)=>{v(null),x(null);try{const i=await fetch("/api/taskforce/workspace/ai-profiles/merge",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({keepId:a,mergeId:t})}),o=await i.json().catch(()=>({}));if(!i.ok){v({type:"error",message:String(o?.error||"Failed to merge AI profiles.")});return}D(null),v({type:"success",message:"AI profiles merged."}),await k(),b({workspaceId:l,profileId:a,reason:"merge"})}catch{v({type:"error",message:"Failed to merge AI profiles."})}},Z=async a=>{if(window.confirm(`Remove ${a.name} from the active roster? This frees an AI profile seat and preserves task and comment history.`)){x(null),v(null);try{const i=await fetch("/api/taskforce/workspace/ai-profiles/archive",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:a.id,reason:"manual_archive"})}),o=await i.json().catch(()=>({}));if(!i.ok){x({profileId:a.id,message:String(o?.error||"Failed to remove AI profile from roster.")});return}(h?.keepId===a.id||h?.mergeId===a.id)&&D(null),v({type:"success",message:"AI profile removed from active roster."}),await k(),b({workspaceId:l,profileId:a.id,reason:"archive"})}catch{x({profileId:a.id,message:"Failed to remove AI profile from roster."})}}},Se=async(a,t)=>{const i=_(t),o=new Set(a.profiles.map(p=>p.surfaceType??"")),d=i??"";if(o.size===1&&o.has(d))return;Y(!0),G(null),v(null);const s=[];try{for(const f of a.profiles){const I=await fetch("/api/taskforce/workspace/ai-profiles/surface-type",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:f.id,surfaceType:i})}),H=await I.json().catch(()=>({}));if(!I.ok||!H?.profile)throw new Error(String(H?.error||"Failed to update AI profile category."));const O=H.profile;s.push({...f,surfaceType:_(O.surfaceType),updatedAt:typeof O.updatedAt=="string"?O.updatedAt:f.updatedAt})}const p=new Map(s.map(f=>[f.id,f]));L(f=>f.map(I=>p.get(I.id)||I));const A=p.get(a.primaryProfile.id)||{...a.primaryProfile,surfaceType:i},Ce=ie(A.surfaceType);j(`${Ce}:${ce(A)}`),v({type:"success",message:"AI profile category updated."});for(const f of s)b({workspaceId:l,profileId:f.id,reason:"update"})}catch(p){G({profileId:a.primaryProfile.id,message:String(p?.message||"Failed to update AI profile category.")})}finally{Y(!1)}},ee=a=>new Promise((t,i)=>{const o=new FileReader;o.onload=()=>t(String(o.result||"")),o.onerror=()=>i(new Error("Failed to read image file.")),o.readAsDataURL(a)}),ae=a=>{const t=String(a?.id||"").trim();t&&L(i=>i.map(o=>o.id===t?{...o,avatarUrl:typeof a.avatarUrl=="string"?a.avatarUrl:null,avatarSourceUrl:typeof a.avatarSourceUrl=="string"?a.avatarSourceUrl:null,avatarRevision:Number.isFinite(Number(a.avatarRevision))?Math.max(0,Math.floor(Number(a.avatarRevision))):o.avatarRevision,avatarUpdatedAt:typeof a.avatarUpdatedAt=="string"?a.avatarUpdatedAt:o.avatarUpdatedAt,updatedAt:typeof a.updatedAt=="string"?a.updatedAt:o.updatedAt}:o))},xe=async(a,t,i)=>{const o=await ee(t),d=i?await ee(i):null,s={profileId:a,displayImage:{dataUrl:o,mimeType:t.type||"application/octet-stream",originalName:t.name||"display-avatar"}};i&&d&&(s.sourceImage={dataUrl:d,mimeType:i.type||"application/octet-stream",originalName:i.name||"source-avatar"});const p=await fetch("/api/taskforce/workspace/ai-profiles/avatar/upload",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),A=await p.json().catch(()=>({}));if(!p.ok||!A?.profile)throw new Error(String(A?.error||"Failed to update AI profile avatar."));ae(A.profile),b({workspaceId:l,profileId:a,reason:"avatar"})},je=async a=>{const t=await fetch("/api/taskforce/workspace/ai-profiles/avatar",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:a,avatarUrl:null,avatarSourceUrl:null})}),i=await t.json().catch(()=>({}));if(!t.ok||!i?.profile)throw new Error(String(i?.error||"Failed to update AI profile avatar."));ae(i.profile),b({workspaceId:l,profileId:a,reason:"avatar"})},Ne=async(a,t)=>{if(!u)return!1;if(!a.type.startsWith("image/"))return g("AI profile photo must be an image file."),!1;M(!0),g(null);try{const i=await oe(a,{maxBytes:5242880});if(i.exceededLimit)throw new Error(a.type==="image/gif"?"Animated GIF AI profile photos must be 5 MB or smaller.":"AI profile photo must be 5 MB or smaller.");const o=i.file;let d=null;if(t){const s=await oe(t,{maxBytes:5242880});if(s.exceededLimit)throw new Error(t.type==="image/gif"?"Animated GIF AI profile photos must be 5 MB or smaller.":"AI profile source photo must be 5 MB or smaller.");d=s.file}return await xe(u.id,o,d),!0}catch(i){return g(String(i?.message||"Failed to update AI profile photo.")),!1}finally{M(!1)}},Ie=async()=>{if(u){M(!0),g(null);try{await je(u.id)}catch(a){g(String(a?.message||"Failed to remove AI profile photo."))}finally{M(!1)}}},N=a=>{const t=String(a||"").trim();if(!t)return"Unknown";const i=Date.parse(t);return Number.isFinite(i)?new Date(i).toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"}):t},re=c.useMemo(()=>{if(!n)return{attributes:[],dates:[]};const a=n.primaryProfile,t=i=>i||"Not set";return{attributes:[{label:"Category",value:a.surfaceType?le(a.surfaceType):""},{label:"Connection",value:a.seatScope?we(a.seatScope):""},{label:"Provider",value:a.provider||""},{label:"Model",value:a.model||""}].map(i=>({...i,value:t(i.value)})),dates:[{label:"Status",value:a.archivedAt?"Retired":"Active"},{label:"Recruited",value:N(a.createdAt)},{label:"Last updated",value:N(a.updatedAt)},{label:"Last active",value:N(a.lastActiveAt)}].map(i=>({...i,value:t(i.value)}))}},[n]),be=!!n&&n.profiles.length>1,te=m&&U&&!ue,$=m?de(me):ve,F=te?"Log into account for Cloud agents":$?`${$.used}/${$.limit===null?"Unlimited":$.limit}`:null,z=!!V;return e.jsxs("section",{className:`${r.agentsModuleRoot} ${z?r.agentsModuleWithTray:""} ${z&&E?r.agentsModuleTrayOpen:""}`.trim(),children:[z&&e.jsxs("aside",{className:`${r.agentTrayPanel} ${E?r.agentTrayPanelOpen:""}`.trim(),"aria-label":"Agent MCP settings tray","aria-hidden":!E,children:[e.jsxs("div",{className:r.agentTrayHeader,children:[e.jsxs("span",{className:r.agentTrayTitle,children:[e.jsx(Re,{size:14}),"MCP Settings"]}),e.jsx("button",{type:"button",className:"tf-control-icon",onClick:he,title:"Collapse agent tray","aria-label":"Collapse agent tray",children:e.jsx($e,{size:16})})]}),e.jsx("div",{className:`${r.agentTrayContent} tf-scrollbar tf-scrollbar--track-transparent`,children:e.jsx("div",{className:r.agentTrayContentInner,children:V})})]}),e.jsx("div",{className:r.agentsModuleContent,children:e.jsx("div",{className:`${r.settingGroup} ${r.agentsModuleGroup}`,children:e.jsxs("div",{children:[e.jsx("h4",{className:r.settingSubTitle,children:"Registered AI Profiles"}),e.jsx("p",{className:`${r.settingsHint} ${r.marginBottom12}`,children:"AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost, or remove inactive profiles from the active roster to free seats while preserving history."}),F&&e.jsx("div",{className:r.aiProfileSeatSummary,children:te?F:`Registered Cloud Agents: ${F}`}),B&&e.jsxs("div",{className:r.settingsHint,children:[e.jsx(Ee,{size:13,className:r.spinner})," Loading profiles…"]}),!B&&S.length===0&&e.jsx("div",{className:r.settingsHint,children:"No AI profiles registered yet."}),!B&&R.length>0&&e.jsxs("div",{className:r.aiProfilesExplorer,children:[e.jsx("div",{className:`${r.aiProfilesListPane} tf-scrollbar tf-scrollbar--track-transparent`,children:e.jsx("div",{className:r.aiProfilesList,children:R.map(a=>e.jsxs("div",{className:r.aiProfilesSection,children:[e.jsx("div",{className:r.aiProfilesSectionHeader,children:a.label}),a.groups.map(t=>{const i=n?.groupId===t.groupId,o=t.primaryProfile;return e.jsxs("div",{role:"button",tabIndex:0,className:`${r.aiProfileGroup} ${t.profiles.length>1?r.aiProfileGroupDuplicate:""} ${i?r.aiProfileGroupSelected:""}`,onClick:()=>j(t.groupId),onKeyDown:d=>{d.key!=="Enter"&&d.key!==" "||(d.preventDefault(),j(t.groupId))},"aria-pressed":i,children:[pe(o.seatScope),e.jsxs("div",{className:r.aiProfileGroupHeader,children:[e.jsx("span",{className:r.aiProfileGroupAvatar,style:{color:o.color},children:o.avatarUrl?e.jsx("img",{src:K(o),alt:""}):e.jsx(ne,{size:22})}),e.jsxs("span",{className:r.aiProfileGroupIdentity,children:[e.jsx("span",{className:r.aiProfileName,children:o.name}),e.jsxs("span",{className:r.aiProfileHandle,children:["@",o.username]}),e.jsx("span",{className:r.aiProfileRole,children:o.role||"Role not set"}),t.profiles.length>1&&e.jsxs("span",{className:r.aiProfileDuplicateBadge,children:[e.jsx(C,{size:11})," ",Oe(t.profiles.length)]})]})]})]},t.groupId)})]},a.section))})}),n&&e.jsx("div",{className:r.aiProfileDetailPane,children:e.jsxs("div",{className:r.aiProfileDetailCard,children:[pe(n.primaryProfile.seatScope,{variant:"detail"}),e.jsxs("div",{className:r.aiProfileDetailHero,children:[e.jsx(Te,{label:"Edit AI profile photo",imageUrl:K(n.primaryProfile),fallback:e.jsx(ne,{size:38}),accentColor:n.primaryProfile.color,size:176,width:153,height:207,radius:6,editBadgeSize:28,editIconSize:14,className:r.aiProfileDetailAvatar,onClick:()=>{g(null),Q(n.primaryProfile.id)}}),e.jsxs("div",{className:r.aiProfileDetailHeading,children:[e.jsx("div",{className:r.aiProfileDetailTitleRow,children:e.jsx("h5",{className:r.aiProfileDetailTitle,children:n.primaryProfile.name})}),e.jsxs("div",{className:r.aiProfileDetailMetaRow,children:[e.jsxs("span",{className:r.aiProfileDetailHandle,children:["@",n.primaryProfile.username]}),n.profiles.length>1&&e.jsxs("span",{className:r.aiProfileDetailLinkedCount,children:[n.profiles.length," linked"]})]}),e.jsxs("div",{className:r.aiProfileDetailRole,children:["Role: ",n.primaryProfile.role||"Not set"]}),n.primaryProfile.description&&e.jsx("div",{className:r.aiProfileDetailDescription,children:n.primaryProfile.description}),e.jsxs("div",{className:r.aiProfileSignatureColor,children:[e.jsx("span",{children:"Signature color"}),e.jsx("span",{className:r.aiProfileSignatureSwatch,style:{backgroundColor:n.primaryProfile.color},"aria-hidden":"true"}),e.jsx("span",{children:n.primaryProfile.color})]})]})]}),e.jsxs("div",{className:r.aiProfileDetailDataList,children:[e.jsx("div",{className:r.aiProfileDetailDataGroup,children:re.attributes.map(a=>e.jsxs("div",{className:r.aiProfileDetailDataRow,children:[e.jsx("span",{className:r.aiProfileDetailDataLabel,children:a.label}),a.label==="Category"?e.jsx("span",{className:r.aiProfileDetailDataValue,children:e.jsxs("select",{className:r.aiProfileStatusSelect,"aria-label":"Agent category",value:n.primaryProfile.surfaceType??"",disabled:ye,onChange:t=>{Se(n,t.target.value)},children:[e.jsx("option",{value:"",children:"Unclassified"}),Me.map(t=>e.jsx("option",{value:t,children:le(t)},t))]})}):e.jsx("span",{className:r.aiProfileDetailDataValue,children:a.value})]},a.label))}),e.jsx("div",{className:`${r.aiProfileDetailDataGroup} ${r.aiProfileDetailDateGroup}`,children:re.dates.map(a=>e.jsxs("div",{className:r.aiProfileDetailDataRow,children:[e.jsx("span",{className:r.aiProfileDetailDataLabel,children:a.label}),a.label==="Status"?e.jsx("span",{className:r.aiProfileDetailDataValue,children:e.jsxs("select",{className:r.aiProfileStatusSelect,"aria-label":"Agent roster status",value:n.primaryProfile.archivedAt?"retired":"active",onChange:t=>{t.target.value==="retire"&&Z(n.primaryProfile)},children:[e.jsx("option",{value:"active",children:"Active"}),n.primaryProfile.archivedAt?e.jsx("option",{value:"retired",children:"Retired"}):e.jsx("option",{value:"retire",children:"Retire from roster"})]})}):e.jsx("span",{className:r.aiProfileDetailDataValue,children:a.value})]},a.label))})]}),W?.profileId===n.primaryProfile.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(C,{size:12}),e.jsx("span",{children:W.message})]}),be?e.jsxs("div",{className:r.aiProfileInstanceSection,children:[e.jsxs("div",{className:r.aiProfileInstanceSectionHeader,children:[e.jsx("span",{children:"Profile instances"}),e.jsx("span",{className:r.settingsHint,children:"Choose a keeper here if duplicates need to be merged."})]}),e.jsx("div",{className:r.aiProfileInstanceList,children:n.profiles.map(a=>e.jsxs("div",{className:r.aiProfileInstanceCard,children:[e.jsxs("div",{className:r.aiProfileInstanceTopRow,children:[e.jsxs("div",{children:[e.jsxs("div",{className:r.aiProfileInstanceName,children:["@",a.username]}),e.jsx("div",{className:r.aiProfileIdChip,children:a.id})]}),h?.keepId===a.id&&e.jsx("span",{className:r.aiProfileKeepBadge,children:"Keeping"})]}),e.jsxs("div",{className:r.aiProfileInstanceMeta,children:[e.jsxs("span",{children:["Created ",N(a.createdAt)]}),e.jsxs("span",{children:["Updated ",N(a.updatedAt)]})]}),h?.keepId!==a.id&&e.jsx("button",{className:r.secondaryHeaderBtn,title:"Keep this profile, merge others into it",onClick:()=>{const t=n.profiles.find(i=>i.id!==a.id)?.id;t&&D({keepId:a.id,mergeId:t})},children:"Keep this"}),T?.profileId===a.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(C,{size:12}),e.jsx("span",{children:T.message})]}),e.jsx("button",{className:r.aiProfileDangerTextButton,title:"Remove this profile from the active roster",onClick:()=>{Z(a)},children:"Remove from roster"})]},a.id))}),h&&n.profiles.some(a=>a.id===h.keepId)&&e.jsxs("div",{className:r.aiProfileMergeActions,children:[e.jsx("button",{className:r.dangerBtn,onClick:()=>{Ae(h.keepId,h.mergeId)},children:"Merge duplicates"}),e.jsx("button",{className:r.secondaryHeaderBtn,onClick:()=>D(null),children:"Cancel"})]})]}):e.jsxs("div",{className:r.aiProfileIdFooter,children:[e.jsx("div",{className:r.aiProfileIdFooterValue,children:n.primaryProfile.id}),T?.profileId===n.primaryProfile.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(C,{size:12}),e.jsx("span",{children:T.message})]})]})]})})]}),w&&e.jsxs("div",{className:`${w.type==="success"?r.successMessage:r.errorMessage} ${r.marginTop12}`,children:[w.type==="success"?e.jsx(Le,{size:14}):e.jsx(C,{size:14}),w.message]})]})})}),e.jsx(ke,{isOpen:!!u,theme:"dark",title:"Edit AI Profile Photo",currentImageUrl:K(u),editorImageUrl:ze(u),fallbackInitial:(u?.name||"AI").charAt(0).toUpperCase(),accept:"image/png,image/jpeg,image/webp,image/gif",busy:X,hasPendingImage:!1,canRemove:!!u?.avatarUrl,error:Pe,notice:null,onClose:()=>{X||(Q(null),g(null))},onApplyImage:Ne,onRemoveImage:Ie})]})}export{qe as AgentsModule};
1
+ import{r as c,j as e}from"./vendor-react-CKJs5o3c.js";import{s as Ue,p as _,u as ie,v as De,w as se,x as le,y as we,t as r,E as Te,z as Me,B as ke,C as fe,F as b,G as oe}from"./index-CWg2olz9.js";import{a0 as Re,x as $e,h as Ee,B as ne,m as C,b as Le,$ as Be,a5 as Ge}from"./vendor-icons-CLnehDTw.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function Fe(l){return typeof l.avatarUrl=="string"&&l.avatarUrl.trim().length>0}function K(l){return l?fe(l.avatarUrl,l.avatarRevision,l.avatarUpdatedAt):""}function ze(l){return l?fe(l.avatarSourceUrl,l.avatarRevision,l.avatarUpdatedAt):""}function He(l){return l.find(Fe)||l[0]}function ce(l){return`${l.seatScope||"unknown"}:${l.name.trim().toLowerCase()}`}function Oe(l){const m=Math.max(0,l-1);return`${m} duplicate${m===1?"":"s"}`}function de(l){return!l||typeof l!="object"?null:{used:Math.max(0,Number(l.used||0)),limit:l.limit===null||l.limit===void 0?null:Math.max(0,Number(l.limit||0)),remaining:l.remaining===null||l.remaining===void 0?null:Math.max(0,Number(l.remaining||0))}}function pe(l,m){const U=m?.variant==="inline"?`${r.aiProfileSeatScopeIcon} ${r.aiProfileSeatScopeIconInline}`:m?.variant==="detail"?`${r.aiProfileSeatScopeIcon} ${r.aiProfileSeatScopeIconDetail}`:r.aiProfileSeatScopeIcon;return l==="cloud_metered"?e.jsx("span",{className:`${U} ${r.aiProfileSeatScopeIconCloud}`,title:"Cloud MCP","aria-label":"Cloud MCP",children:e.jsx(Be,{size:20})}):l==="local_unmetered"?e.jsx("span",{className:`${U} ${r.aiProfileSeatScopeIconLocal}`,title:"Local MCP","aria-label":"Local MCP",children:e.jsx(Ge,{size:20})}):null}function qe({workspaceId:l,cloudAuthConfigured:m=!1,authSessionResolved:U=!1,isAuthenticated:ue=!1,cloudAiProfileSeatUsage:me=null,agentTrayOpen:E=!1,onCloseAgentTray:he,mcpSettingsNode:V=null}){const[S,L]=c.useState([]),[ve,ge]=c.useState(null),[B,J]=c.useState(!1),[h,D]=c.useState(null),[w,v]=c.useState(null),[T,x]=c.useState(null),[W,G]=c.useState(null),[ye,Y]=c.useState(!1),[P,j]=c.useState(null),[q,Q]=c.useState(null),[X,M]=c.useState(!1),[Pe,g]=c.useState(null),k=c.useCallback(async()=>{if(l){J(!0);try{const a=`/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(l)}&kind=agent`,t=await fetch(a,{credentials:"include"}),i=t.ok?await t.json().catch(()=>({})):{},o=de(i?.aiProfileSeatUsage),d=Array.isArray(i?.assignees)?i.assignees.filter(s=>s.kind==="agent").map(s=>({id:String(s.value||""),name:String(s.label||s.value||"Unknown Agent"),username:String(s.username||s.value||""),icon:String(s.icon||"Bot"),color:String(s.color||"#6B7280"),avatarUrl:typeof s.avatarUrl=="string"?s.avatarUrl:null,avatarSourceUrl:typeof s.avatarSourceUrl=="string"?s.avatarSourceUrl:null,avatarRevision:Number.isFinite(Number(s.avatarRevision))?Math.max(0,Math.floor(Number(s.avatarRevision))):0,avatarUpdatedAt:typeof s.avatarUpdatedAt=="string"?s.avatarUpdatedAt:null,kind:String(s.kind||"agent"),description:typeof s.description=="string"?s.description:null,role:typeof s.role=="string"?s.role:null,provider:typeof s.provider=="string"?s.provider:null,model:typeof s.model=="string"?s.model:null,surfaceType:_(s.surfaceType),seatScope:Ue(s.seatScope),archivedAt:typeof s.archivedAt=="string"?s.archivedAt:null,createdAt:String(s.createdAt||""),updatedAt:String(s.updatedAt||s.createdAt||""),lastActiveAt:typeof s.lastActiveAt=="string"?s.lastActiveAt:null})):[];L(d),ge(o),x(s=>s&&!d.some(p=>p.id===s.profileId)?null:s),G(s=>s&&!d.some(p=>p.id===s.profileId)?null:s)}finally{J(!1)}}},[l]);c.useEffect(()=>{k()},[k]);const R=c.useMemo(()=>{const a=new Map;for(const t of S){const i=ie(t.surfaceType);a.has(i)||a.set(i,new Map);const o=a.get(i),d=ce(t);o.has(d)||o.set(d,[]),o.get(d).push(t)}return De.map(t=>({section:t,label:se(t),groups:Array.from(a.get(t)?.entries()||[]).map(([i,o])=>({groupId:`${t}:${i}`,section:t,sectionLabel:se(t),profiles:o,primaryProfile:He(o)}))})).filter(t=>t.groups.length>0)},[S]),y=c.useMemo(()=>R.flatMap(a=>a.groups),[R]),n=c.useMemo(()=>y.find(a=>a.groupId===P)||y[0]||null,[y,P]),u=c.useMemo(()=>S.find(a=>a.id===q)||null,[S,q]);c.useEffect(()=>{if(!y.length){P!==null&&j(null);return}(!P||!y.some(a=>a.groupId===P))&&j(y[0].groupId)},[y,P]);const Ae=async(a,t)=>{v(null),x(null);try{const i=await fetch("/api/taskforce/workspace/ai-profiles/merge",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({keepId:a,mergeId:t})}),o=await i.json().catch(()=>({}));if(!i.ok){v({type:"error",message:String(o?.error||"Failed to merge AI profiles.")});return}D(null),v({type:"success",message:"AI profiles merged."}),await k(),b({workspaceId:l,profileId:a,reason:"merge"})}catch{v({type:"error",message:"Failed to merge AI profiles."})}},Z=async a=>{if(window.confirm(`Remove ${a.name} from the active roster? This frees an AI profile seat and preserves task and comment history.`)){x(null),v(null);try{const i=await fetch("/api/taskforce/workspace/ai-profiles/archive",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:a.id,reason:"manual_archive"})}),o=await i.json().catch(()=>({}));if(!i.ok){x({profileId:a.id,message:String(o?.error||"Failed to remove AI profile from roster.")});return}(h?.keepId===a.id||h?.mergeId===a.id)&&D(null),v({type:"success",message:"AI profile removed from active roster."}),await k(),b({workspaceId:l,profileId:a.id,reason:"archive"})}catch{x({profileId:a.id,message:"Failed to remove AI profile from roster."})}}},Se=async(a,t)=>{const i=_(t),o=new Set(a.profiles.map(p=>p.surfaceType??"")),d=i??"";if(o.size===1&&o.has(d))return;Y(!0),G(null),v(null);const s=[];try{for(const f of a.profiles){const I=await fetch("/api/taskforce/workspace/ai-profiles/surface-type",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:f.id,surfaceType:i})}),H=await I.json().catch(()=>({}));if(!I.ok||!H?.profile)throw new Error(String(H?.error||"Failed to update AI profile category."));const O=H.profile;s.push({...f,surfaceType:_(O.surfaceType),updatedAt:typeof O.updatedAt=="string"?O.updatedAt:f.updatedAt})}const p=new Map(s.map(f=>[f.id,f]));L(f=>f.map(I=>p.get(I.id)||I));const A=p.get(a.primaryProfile.id)||{...a.primaryProfile,surfaceType:i},Ce=ie(A.surfaceType);j(`${Ce}:${ce(A)}`),v({type:"success",message:"AI profile category updated."});for(const f of s)b({workspaceId:l,profileId:f.id,reason:"update"})}catch(p){G({profileId:a.primaryProfile.id,message:String(p?.message||"Failed to update AI profile category.")})}finally{Y(!1)}},ee=a=>new Promise((t,i)=>{const o=new FileReader;o.onload=()=>t(String(o.result||"")),o.onerror=()=>i(new Error("Failed to read image file.")),o.readAsDataURL(a)}),ae=a=>{const t=String(a?.id||"").trim();t&&L(i=>i.map(o=>o.id===t?{...o,avatarUrl:typeof a.avatarUrl=="string"?a.avatarUrl:null,avatarSourceUrl:typeof a.avatarSourceUrl=="string"?a.avatarSourceUrl:null,avatarRevision:Number.isFinite(Number(a.avatarRevision))?Math.max(0,Math.floor(Number(a.avatarRevision))):o.avatarRevision,avatarUpdatedAt:typeof a.avatarUpdatedAt=="string"?a.avatarUpdatedAt:o.avatarUpdatedAt,updatedAt:typeof a.updatedAt=="string"?a.updatedAt:o.updatedAt}:o))},xe=async(a,t,i)=>{const o=await ee(t),d=i?await ee(i):null,s={profileId:a,displayImage:{dataUrl:o,mimeType:t.type||"application/octet-stream",originalName:t.name||"display-avatar"}};i&&d&&(s.sourceImage={dataUrl:d,mimeType:i.type||"application/octet-stream",originalName:i.name||"source-avatar"});const p=await fetch("/api/taskforce/workspace/ai-profiles/avatar/upload",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),A=await p.json().catch(()=>({}));if(!p.ok||!A?.profile)throw new Error(String(A?.error||"Failed to update AI profile avatar."));ae(A.profile),b({workspaceId:l,profileId:a,reason:"avatar"})},je=async a=>{const t=await fetch("/api/taskforce/workspace/ai-profiles/avatar",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({profileId:a,avatarUrl:null,avatarSourceUrl:null})}),i=await t.json().catch(()=>({}));if(!t.ok||!i?.profile)throw new Error(String(i?.error||"Failed to update AI profile avatar."));ae(i.profile),b({workspaceId:l,profileId:a,reason:"avatar"})},Ne=async(a,t)=>{if(!u)return!1;if(!a.type.startsWith("image/"))return g("AI profile photo must be an image file."),!1;M(!0),g(null);try{const i=await oe(a,{maxBytes:5242880});if(i.exceededLimit)throw new Error(a.type==="image/gif"?"Animated GIF AI profile photos must be 5 MB or smaller.":"AI profile photo must be 5 MB or smaller.");const o=i.file;let d=null;if(t){const s=await oe(t,{maxBytes:5242880});if(s.exceededLimit)throw new Error(t.type==="image/gif"?"Animated GIF AI profile photos must be 5 MB or smaller.":"AI profile source photo must be 5 MB or smaller.");d=s.file}return await xe(u.id,o,d),!0}catch(i){return g(String(i?.message||"Failed to update AI profile photo.")),!1}finally{M(!1)}},Ie=async()=>{if(u){M(!0),g(null);try{await je(u.id)}catch(a){g(String(a?.message||"Failed to remove AI profile photo."))}finally{M(!1)}}},N=a=>{const t=String(a||"").trim();if(!t)return"Unknown";const i=Date.parse(t);return Number.isFinite(i)?new Date(i).toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"}):t},re=c.useMemo(()=>{if(!n)return{attributes:[],dates:[]};const a=n.primaryProfile,t=i=>i||"Not set";return{attributes:[{label:"Category",value:a.surfaceType?le(a.surfaceType):""},{label:"Connection",value:a.seatScope?we(a.seatScope):""},{label:"Provider",value:a.provider||""},{label:"Model",value:a.model||""}].map(i=>({...i,value:t(i.value)})),dates:[{label:"Status",value:a.archivedAt?"Retired":"Active"},{label:"Recruited",value:N(a.createdAt)},{label:"Last updated",value:N(a.updatedAt)},{label:"Last active",value:N(a.lastActiveAt)}].map(i=>({...i,value:t(i.value)}))}},[n]),be=!!n&&n.profiles.length>1,te=m&&U&&!ue,$=m?de(me):ve,F=te?"Log into account for Cloud agents":$?`${$.used}/${$.limit===null?"Unlimited":$.limit}`:null,z=!!V;return e.jsxs("section",{className:`${r.agentsModuleRoot} ${z?r.agentsModuleWithTray:""} ${z&&E?r.agentsModuleTrayOpen:""}`.trim(),children:[z&&e.jsxs("aside",{className:`${r.agentTrayPanel} ${E?r.agentTrayPanelOpen:""}`.trim(),"aria-label":"Agent MCP settings tray","aria-hidden":!E,children:[e.jsxs("div",{className:r.agentTrayHeader,children:[e.jsxs("span",{className:r.agentTrayTitle,children:[e.jsx(Re,{size:14}),"MCP Settings"]}),e.jsx("button",{type:"button",className:"tf-control-icon",onClick:he,title:"Collapse agent tray","aria-label":"Collapse agent tray",children:e.jsx($e,{size:16})})]}),e.jsx("div",{className:`${r.agentTrayContent} tf-scrollbar tf-scrollbar--track-transparent`,children:e.jsx("div",{className:r.agentTrayContentInner,children:V})})]}),e.jsx("div",{className:r.agentsModuleContent,children:e.jsx("div",{className:`${r.settingGroup} ${r.agentsModuleGroup}`,children:e.jsxs("div",{children:[e.jsx("h4",{className:r.settingSubTitle,children:"Registered AI Profiles"}),e.jsx("p",{className:`${r.settingsHint} ${r.marginBottom12}`,children:"AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost, or remove inactive profiles from the active roster to free seats while preserving history."}),F&&e.jsx("div",{className:r.aiProfileSeatSummary,children:te?F:`Registered Cloud Agents: ${F}`}),B&&e.jsxs("div",{className:r.settingsHint,children:[e.jsx(Ee,{size:13,className:r.spinner})," Loading profiles…"]}),!B&&S.length===0&&e.jsx("div",{className:r.settingsHint,children:"No AI profiles registered yet."}),!B&&R.length>0&&e.jsxs("div",{className:r.aiProfilesExplorer,children:[e.jsx("div",{className:`${r.aiProfilesListPane} tf-scrollbar tf-scrollbar--track-transparent`,children:e.jsx("div",{className:r.aiProfilesList,children:R.map(a=>e.jsxs("div",{className:r.aiProfilesSection,children:[e.jsx("div",{className:r.aiProfilesSectionHeader,children:a.label}),a.groups.map(t=>{const i=n?.groupId===t.groupId,o=t.primaryProfile;return e.jsxs("div",{role:"button",tabIndex:0,className:`${r.aiProfileGroup} ${t.profiles.length>1?r.aiProfileGroupDuplicate:""} ${i?r.aiProfileGroupSelected:""}`,onClick:()=>j(t.groupId),onKeyDown:d=>{d.key!=="Enter"&&d.key!==" "||(d.preventDefault(),j(t.groupId))},"aria-pressed":i,children:[pe(o.seatScope),e.jsxs("div",{className:r.aiProfileGroupHeader,children:[e.jsx("span",{className:r.aiProfileGroupAvatar,style:{color:o.color},children:o.avatarUrl?e.jsx("img",{src:K(o),alt:""}):e.jsx(ne,{size:22})}),e.jsxs("span",{className:r.aiProfileGroupIdentity,children:[e.jsx("span",{className:r.aiProfileName,children:o.name}),e.jsxs("span",{className:r.aiProfileHandle,children:["@",o.username]}),e.jsx("span",{className:r.aiProfileRole,children:o.role||"Role not set"}),t.profiles.length>1&&e.jsxs("span",{className:r.aiProfileDuplicateBadge,children:[e.jsx(C,{size:11})," ",Oe(t.profiles.length)]})]})]})]},t.groupId)})]},a.section))})}),n&&e.jsx("div",{className:r.aiProfileDetailPane,children:e.jsxs("div",{className:r.aiProfileDetailCard,children:[pe(n.primaryProfile.seatScope,{variant:"detail"}),e.jsxs("div",{className:r.aiProfileDetailHero,children:[e.jsx(Te,{label:"Edit AI profile photo",imageUrl:K(n.primaryProfile),fallback:e.jsx(ne,{size:38}),accentColor:n.primaryProfile.color,size:176,width:153,height:207,radius:6,editBadgeSize:28,editIconSize:14,className:r.aiProfileDetailAvatar,onClick:()=>{g(null),Q(n.primaryProfile.id)}}),e.jsxs("div",{className:r.aiProfileDetailHeading,children:[e.jsx("div",{className:r.aiProfileDetailTitleRow,children:e.jsx("h5",{className:r.aiProfileDetailTitle,children:n.primaryProfile.name})}),e.jsxs("div",{className:r.aiProfileDetailMetaRow,children:[e.jsxs("span",{className:r.aiProfileDetailHandle,children:["@",n.primaryProfile.username]}),n.profiles.length>1&&e.jsxs("span",{className:r.aiProfileDetailLinkedCount,children:[n.profiles.length," linked"]})]}),e.jsxs("div",{className:r.aiProfileDetailRole,children:["Role: ",n.primaryProfile.role||"Not set"]}),n.primaryProfile.description&&e.jsx("div",{className:r.aiProfileDetailDescription,children:n.primaryProfile.description}),e.jsxs("div",{className:r.aiProfileSignatureColor,children:[e.jsx("span",{children:"Signature color"}),e.jsx("span",{className:r.aiProfileSignatureSwatch,style:{backgroundColor:n.primaryProfile.color},"aria-hidden":"true"}),e.jsx("span",{children:n.primaryProfile.color})]})]})]}),e.jsxs("div",{className:r.aiProfileDetailDataList,children:[e.jsx("div",{className:r.aiProfileDetailDataGroup,children:re.attributes.map(a=>e.jsxs("div",{className:r.aiProfileDetailDataRow,children:[e.jsx("span",{className:r.aiProfileDetailDataLabel,children:a.label}),a.label==="Category"?e.jsx("span",{className:r.aiProfileDetailDataValue,children:e.jsxs("select",{className:r.aiProfileStatusSelect,"aria-label":"Agent category",value:n.primaryProfile.surfaceType??"",disabled:ye,onChange:t=>{Se(n,t.target.value)},children:[e.jsx("option",{value:"",children:"Unclassified"}),Me.map(t=>e.jsx("option",{value:t,children:le(t)},t))]})}):e.jsx("span",{className:r.aiProfileDetailDataValue,children:a.value})]},a.label))}),e.jsx("div",{className:`${r.aiProfileDetailDataGroup} ${r.aiProfileDetailDateGroup}`,children:re.dates.map(a=>e.jsxs("div",{className:r.aiProfileDetailDataRow,children:[e.jsx("span",{className:r.aiProfileDetailDataLabel,children:a.label}),a.label==="Status"?e.jsx("span",{className:r.aiProfileDetailDataValue,children:e.jsxs("select",{className:r.aiProfileStatusSelect,"aria-label":"Agent roster status",value:n.primaryProfile.archivedAt?"retired":"active",onChange:t=>{t.target.value==="retire"&&Z(n.primaryProfile)},children:[e.jsx("option",{value:"active",children:"Active"}),n.primaryProfile.archivedAt?e.jsx("option",{value:"retired",children:"Retired"}):e.jsx("option",{value:"retire",children:"Retire from roster"})]})}):e.jsx("span",{className:r.aiProfileDetailDataValue,children:a.value})]},a.label))})]}),W?.profileId===n.primaryProfile.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(C,{size:12}),e.jsx("span",{children:W.message})]}),be?e.jsxs("div",{className:r.aiProfileInstanceSection,children:[e.jsxs("div",{className:r.aiProfileInstanceSectionHeader,children:[e.jsx("span",{children:"Profile instances"}),e.jsx("span",{className:r.settingsHint,children:"Choose a keeper here if duplicates need to be merged."})]}),e.jsx("div",{className:r.aiProfileInstanceList,children:n.profiles.map(a=>e.jsxs("div",{className:r.aiProfileInstanceCard,children:[e.jsxs("div",{className:r.aiProfileInstanceTopRow,children:[e.jsxs("div",{children:[e.jsxs("div",{className:r.aiProfileInstanceName,children:["@",a.username]}),e.jsx("div",{className:r.aiProfileIdChip,children:a.id})]}),h?.keepId===a.id&&e.jsx("span",{className:r.aiProfileKeepBadge,children:"Keeping"})]}),e.jsxs("div",{className:r.aiProfileInstanceMeta,children:[e.jsxs("span",{children:["Created ",N(a.createdAt)]}),e.jsxs("span",{children:["Updated ",N(a.updatedAt)]})]}),h?.keepId!==a.id&&e.jsx("button",{className:r.secondaryHeaderBtn,title:"Keep this profile, merge others into it",onClick:()=>{const t=n.profiles.find(i=>i.id!==a.id)?.id;t&&D({keepId:a.id,mergeId:t})},children:"Keep this"}),T?.profileId===a.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(C,{size:12}),e.jsx("span",{children:T.message})]}),e.jsx("button",{className:r.aiProfileDangerTextButton,title:"Remove this profile from the active roster",onClick:()=>{Z(a)},children:"Remove from roster"})]},a.id))}),h&&n.profiles.some(a=>a.id===h.keepId)&&e.jsxs("div",{className:r.aiProfileMergeActions,children:[e.jsx("button",{className:r.dangerBtn,onClick:()=>{Ae(h.keepId,h.mergeId)},children:"Merge duplicates"}),e.jsx("button",{className:r.secondaryHeaderBtn,onClick:()=>D(null),children:"Cancel"})]})]}):e.jsxs("div",{className:r.aiProfileIdFooter,children:[e.jsx("div",{className:r.aiProfileIdFooterValue,children:n.primaryProfile.id}),T?.profileId===n.primaryProfile.id&&e.jsxs("div",{className:r.aiProfileInlineError,role:"alert",children:[e.jsx(C,{size:12}),e.jsx("span",{children:T.message})]})]})]})})]}),w&&e.jsxs("div",{className:`${w.type==="success"?r.successMessage:r.errorMessage} ${r.marginTop12}`,children:[w.type==="success"?e.jsx(Le,{size:14}):e.jsx(C,{size:14}),w.message]})]})})}),e.jsx(ke,{isOpen:!!u,theme:"dark",title:"Edit AI Profile Photo",currentImageUrl:K(u),editorImageUrl:ze(u),fallbackInitial:(u?.name||"AI").charAt(0).toUpperCase(),accept:"image/png,image/jpeg,image/webp,image/gif",busy:X,hasPendingImage:!1,canRemove:!!u?.avatarUrl,error:Pe,notice:null,onClose:()=>{X||(Q(null),g(null))},onApplyImage:Ne,onRemoveImage:Ie})]})}export{qe as AgentsModule};
@@ -0,0 +1,3 @@
1
+ import{j as t,r as s,R as aa}from"./vendor-react-CKJs5o3c.js";import{R as Fn,f as ge,e as Rt,g as Hn,t as za,M as na}from"./index-CWg2olz9.js";import{a7 as Ua,x as Dn,w as On,p as zn,P as Un,c as sa,T as Ga,r as Gn,h as Ka,an as Kn,R as Wn,ao as Yn,ap as Xn,aq as Vn,ar as ia,C as la,as as qn,at as Za,au as Qa,av as en,aw as tn,g as Jn,f as Zn,b as ca}from"./vendor-icons-CLnehDTw.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function Qn({copied:r,disabled:l=!1,label:f,onClick:p,title:b="Copy image reference",ariaLabel:x,className:L=""}){return t.jsx(Fn,{copied:r,disabled:l,label:"",onClick:p,title:b,ariaLabel:x,className:L,children:f})}const es="_shell_17mlo_1",ts="_shellWithImageTray_17mlo_18",as="_shellImageTrayOpen_17mlo_22",ns="_imageTrayPanel_17mlo_26",ss="_imageTrayPanelOpen_17mlo_50",rs="_imageTrayHeader_17mlo_58",os="_imageTrayTitle_17mlo_73",is="_imageTraySearch_17mlo_85",ls="_imageTraySearchIcon_17mlo_91",cs="_imageTraySearchInput_17mlo_100",ds="_imageTrayList_17mlo_117",us="_imageTrayState_17mlo_129",ms="_imageTrayStateError_17mlo_130",fs="_imageTrayItem_17mlo_142",hs="_imageTrayItemActive_17mlo_164",ps="_imageTrayThumb_17mlo_174",gs="_imageTrayItemBody_17mlo_191",ys="_imageTrayItemTitle_17mlo_199",bs="_imageTrayItemTask_17mlo_200",xs="_imageTrayItemMetaDetails_17mlo_201",vs="_imageTrayItemMeta_17mlo_201",_s="_imageTrayItemReference_17mlo_239",Is="_panel_17mlo_246",ws="_sessionPanel_17mlo_252",ks="_detailPanel_17mlo_253",Ss="_sessionContextBar_17mlo_259",Ns="_sessionContextLeft_17mlo_270",Cs="_sessionContextRight_17mlo_271",js="_sessionContextLabel_17mlo_286",Ts="_sessionContextSpacer_17mlo_292",$s="_canvasPanel_17mlo_297",Rs="_canvasWorkspace_17mlo_304",Ps="_canvasMain_17mlo_311",Bs="_panelHeader_17mlo_318",Es="_panelHeaderText_17mlo_327",As="_panelTitle_17mlo_331",Ms="_canvasHeading_17mlo_335",Ls="_sessionActions_17mlo_339",Fs="_sessionList_17mlo_347",Hs="_annotationList_17mlo_348",Ds="_markerHelpModalBody_17mlo_363",Os="_openImageModalBody_17mlo_369",zs="_openImageField_17mlo_375",Us="_openImageActions_17mlo_379",Gs="_markerHelpItem_17mlo_385",Ks="_markerHelpHeader_17mlo_393",Ws="_markerHelpExample_17mlo_405",Ys="_sessionCard_17mlo_409",Xs="_annotationCard_17mlo_410",Vs="_sessionEmptyState_17mlo_427",qs="_sessionCardButton_17mlo_434",Js="_annotationCardButton_17mlo_444",Zs="_sessionCardBody_17mlo_454",Qs="_sessionCardActive_17mlo_460",er="_annotationCardActive_17mlo_461",tr="_annotationMeta_17mlo_471",ar="_annotationInstructionPreview_17mlo_478",nr="_annotationPreviewFooter_17mlo_486",sr="_annotationInstructionEditor_17mlo_493",rr="_annotationTypeField_17mlo_499",or="_annotationInstructionButton_17mlo_505",ir="_annotationInstructionField_17mlo_514",lr="_sessionMeta_17mlo_518",cr="_sessionTitle_17mlo_525",dr="_annotationTitle_17mlo_526",ur="_sessionTimestamp_17mlo_532",mr="_annotationKind_17mlo_533",fr="_annotationInstructionTypeIcon_17mlo_537",hr="_sessionInstructionPreview_17mlo_543",pr="_sessionInstructionEditor_17mlo_551",gr="_sessionCardFooter_17mlo_557",yr="_toolRail_17mlo_564",br="_canvasToolRail_17mlo_573",xr="_toolbarCluster_17mlo_594",vr="_toolbarViewportCluster_17mlo_601",_r="_toolbarSeparator_17mlo_605",Ir="_toolBtn_17mlo_611",wr="_toolRailButton_17mlo_615",kr="_toolbarButton_17mlo_625",Sr="_toolBtnActive_17mlo_630",Nr="_toolbarActions_17mlo_637",Cr="_toolbarSelectionActions_17mlo_646",jr="_toolbarColorPicker_17mlo_654",Tr="_colorPickerButton_17mlo_658",$r="_colorPickerSwatch_17mlo_663",Rr="_colorPickerPopover_17mlo_671",Pr="_colorOption_17mlo_686",Br="_colorOptionActive_17mlo_696",Er="_toolbarUtilities_17mlo_703",Ar="_toolbarGeometryFields_17mlo_711",Mr="_toolbarGeometryField_17mlo_711",Lr="_toolbarGeometryLabel_17mlo_724",Fr="_toolbarGeometryInput_17mlo_730",Hr="_iconButton_17mlo_735",Dr="_ghostBtn_17mlo_740",Or="_payloadBtn_17mlo_741",zr="_backToTaskBtn_17mlo_742",Ur="_canvasScroller_17mlo_762",Gr="_canvasFrame_17mlo_778",Kr="_canvasMedia_17mlo_785",Wr="_canvasStatusOverlay_17mlo_793",Yr="_canvasStatusCard_17mlo_805",Xr="_canvasImage_17mlo_818",Vr="_overlay_17mlo_825",qr="_overlaySelect_17mlo_831",Jr="_overlayPan_17mlo_835",Zr="_overlaySvg_17mlo_839",Qr="_overlayHitLayer_17mlo_848",eo="_arrowHitArea_17mlo_857",to="_canvasHandleHit_17mlo_864",ao="_canvasResizeHandleHit_17mlo_871",no="_canvasHandleVisible_17mlo_875",so="_pin_17mlo_892",ro="_note_17mlo_893",oo="_annotationNumberBadge_17mlo_910",io="_box_17mlo_935",lo="_boxNumberBadge_17mlo_944",co="_arrowNumberBadge_17mlo_950",uo="_boxSurface_17mlo_954",mo="_selected_17mlo_967",fo="_textInput_17mlo_984",ho="_textArea_17mlo_985",po="_select_17mlo_967",go="_sessionTitleInput_17mlo_992",yo="_sessionInstructionField_17mlo_997",bo="_detailEmpty_17mlo_1006",xo="_emptyState_17mlo_1007",vo="_payloadModalBody_17mlo_1019",_o="_payloadModalToolbar_17mlo_1026",Io="_payloadViewToggle_17mlo_1033",wo="_payloadModalActions_17mlo_1034",ko="_payloadModalPreview_17mlo_1041",So="_statusBar_17mlo_1056",No="_annotationSummary_17mlo_1068",a={shell:es,shellWithImageTray:ts,shellImageTrayOpen:as,imageTrayPanel:ns,imageTrayPanelOpen:ss,imageTrayHeader:rs,imageTrayTitle:os,imageTraySearch:is,imageTraySearchIcon:ls,imageTraySearchInput:cs,imageTrayList:ds,imageTrayState:us,imageTrayStateError:ms,imageTrayItem:fs,imageTrayItemActive:hs,imageTrayThumb:ps,imageTrayItemBody:gs,imageTrayItemTitle:ys,imageTrayItemTask:bs,imageTrayItemMetaDetails:xs,imageTrayItemMeta:vs,imageTrayItemReference:_s,panel:Is,sessionPanel:ws,detailPanel:ks,sessionContextBar:Ss,sessionContextLeft:Ns,sessionContextRight:Cs,sessionContextLabel:js,sessionContextSpacer:Ts,canvasPanel:$s,canvasWorkspace:Rs,canvasMain:Ps,panelHeader:Bs,panelHeaderText:Es,panelTitle:As,canvasHeading:Ms,sessionActions:Ls,sessionList:Fs,annotationList:Hs,markerHelpModalBody:Ds,openImageModalBody:Os,openImageField:zs,openImageActions:Us,markerHelpItem:Gs,markerHelpHeader:Ks,markerHelpExample:Ws,sessionCard:Ys,annotationCard:Xs,sessionEmptyState:Vs,sessionCardButton:qs,annotationCardButton:Js,sessionCardBody:Zs,sessionCardActive:Qs,annotationCardActive:er,annotationMeta:tr,annotationInstructionPreview:ar,annotationPreviewFooter:nr,annotationInstructionEditor:sr,annotationTypeField:rr,annotationInstructionButton:or,annotationInstructionField:ir,sessionMeta:lr,sessionTitle:cr,annotationTitle:dr,sessionTimestamp:ur,annotationKind:mr,annotationInstructionTypeIcon:fr,sessionInstructionPreview:hr,sessionInstructionEditor:pr,sessionCardFooter:gr,toolRail:yr,canvasToolRail:br,toolbarCluster:xr,toolbarViewportCluster:vr,toolbarSeparator:_r,toolBtn:Ir,toolRailButton:wr,toolbarButton:kr,toolBtnActive:Sr,toolbarActions:Nr,toolbarSelectionActions:Cr,toolbarColorPicker:jr,colorPickerButton:Tr,colorPickerSwatch:$r,colorPickerPopover:Rr,colorOption:Pr,colorOptionActive:Br,toolbarUtilities:Er,toolbarGeometryFields:Ar,toolbarGeometryField:Mr,toolbarGeometryLabel:Lr,toolbarGeometryInput:Fr,iconButton:Hr,ghostBtn:Dr,payloadBtn:Or,backToTaskBtn:zr,canvasScroller:Ur,canvasFrame:Gr,canvasMedia:Kr,canvasStatusOverlay:Wr,canvasStatusCard:Yr,canvasImage:Xr,overlay:Vr,overlaySelect:qr,overlayPan:Jr,overlaySvg:Zr,overlayHitLayer:Qr,arrowHitArea:eo,canvasHandleHit:to,canvasResizeHandleHit:ao,canvasHandleVisible:no,pin:so,note:ro,annotationNumberBadge:oo,box:io,boxNumberBadge:lo,arrowNumberBadge:co,boxSurface:uo,selected:mo,textInput:fo,textArea:ho,select:po,sessionTitleInput:go,sessionInstructionField:yo,detailEmpty:bo,emptyState:xo,payloadModalBody:vo,payloadModalToolbar:_o,payloadViewToggle:Io,payloadModalActions:wo,payloadModalPreview:ko,statusBar:So,annotationSummary:No},Wa=[{value:"review",label:"Review"},{value:"change",label:"Change"},{value:"question",label:"Question"}],Ya=[{value:"select",label:"Select",icon:qn},{value:"pin",label:"Pin",icon:Za},{value:"box",label:"Box",icon:Qa},{value:"arrow",label:"Arrow",icon:en},{value:"text-note",label:"Note",icon:tn}],Co={pin:Za,box:Qa,arrow:en,"text-note":tn},jo={pin:"Pin",box:"Box",arrow:"Arrow","text-note":"Note"},To={review:ia,change:ca,question:la,issue:la,idea:ia},Pt={select:{short:"Select and edit existing markers.",detail:"Use Select to click, drag, reorder, resize, and update markers that are already on the image.",example:"Example: move an existing marker after the screenshot changes."},pin:{short:"Mark a precise spot.",detail:"Use Pin when feedback points to one exact location instead of a broader area.",example:'Example: "This icon is misaligned by 2px."'},box:{short:"Mark an area or component.",detail:"Use Box when the feedback applies to a whole region, card, panel, or bounded UI block.",example:'Example: "This whole card needs tighter padding and a stronger border."'},arrow:{short:"Show direction or relationship.",detail:"Use Arrow when you need to show movement, attachment, flow, or source-to-target intent.",example:'Example: "This tooltip should anchor to this button, not the panel."'},"text-note":{short:"Add a comment-style point marker.",detail:"Use Note when you want a point marker that reads more like a comment or open question.",example:'Example: "Ask design whether this badge should stay."'}},ct={question:"#0f766e",change:"#2563eb",issue:"#dc2626",idea:"#d97706",review:"#7c3aed"},$o=["#7c3aed","#2563eb","#0f766e","#dc2626","#d97706","#111827"],ce="review";function Ke(r){const l=String(r.displayName||"").trim();return l?`${l} review`:"Annotated session"}function an(){return`annotation-${Math.random().toString(36).slice(2,10)}`}function I(r){return!Number.isFinite(r)||r<=0?0:r>=1?1:r}function Se(r){return I(Math.max(.02,r))}function Ge(r){return r?[String(r.taskId||"").trim(),String(r.assetId||"").trim(),String(r.path||"").trim()].join("::"):""}function nn(r){if(!(r instanceof HTMLElement))return!1;const l=r.tagName.toLowerCase();return r.isContentEditable?!0:l==="input"||l==="textarea"||l==="select"}function Ro(r){if(!(r instanceof HTMLElement))return!1;if(nn(r))return!0;const l=r.tagName.toLowerCase();return l==="button"||l==="a"||r.getAttribute("role")==="button"}function ke(r){return r.map((l,f)=>({...l,order:f}))}function ra(r){if(!r)return"Unsaved";const l=new Date(r);return Number.isNaN(l.getTime())?"Unsaved":l.toLocaleString()}function Po(r){const l=typeof r=="number"&&Number.isFinite(r)?Math.max(0,r):0;return l<1024?`${l}B`:l<1024*1024?`${(l/1024).toFixed(1)}KB`:`${(l/(1024*1024)).toFixed(1)}MB`}function Bo(r){if(!r)return"";const l=new Date(r);if(Number.isNaN(l.getTime()))return"";const p=new Date().getTime()-l.getTime(),b=Math.floor(p/(1e3*60*60*24));return b<=0?"Today":b===1?"Yesterday":b<7?`${b}d ago`:b<30?`${Math.floor(b/7)}w ago`:l.toLocaleDateString()}function Bt(r){const l=String(r.createdByActor?.label||"").trim();return l||null}function Eo(r){const l=String(r||"").trim().replace(/\s+/g," ");return l?l.length>110?`${l.slice(0,107)}...`:l:""}function Xa(r,l,f,p=ct[ce]){const b={id:an(),order:0,instruction:"",markerType:ce,color:p};if(r==="pin")return{...b,kind:r,x:l.x,y:l.y};if(r==="text-note")return{...b,kind:r,x:l.x,y:l.y};if(r==="box"){const L=f||l;return{...b,kind:r,x:I(Math.min(l.x,L.x)),y:I(Math.min(l.y,L.y)),width:Se(Math.abs(L.x-l.x)),height:Se(Math.abs(L.y-l.y))}}const x=f||l;return{...b,kind:"arrow",x:l.x,y:l.y,x2:x.x,y2:x.y}}function oa(r){return r.color?r.color:ct[r.markerType||ce]}function Ao(r,l){const f=Math.max(l.width,1),p=Math.max(l.height,1),b=r.x*f,x=r.y*p,L=r.x2*f,ne=r.y2*p,se=L-b,dt=ne-x,Ne=Math.hypot(se,dt)||1,We=se/Ne,Ce=dt/Ne,G=Math.max(10,Math.min(16,Ne-2)),ye=G*.62,je=L-We*G,$=ne-Ce*G,ut=-Ce,be=We;return{shaftX1:b,shaftY1:x,shaftX2:je,shaftY2:$,headPoints:[`${L},${ne}`,`${je+ut*ye},${$+be*ye}`,`${je-ut*ye},${$-be*ye}`].join(" ")}}function Mo(r,l){return{...r,markerType:l,color:r.color||ct[l]}}function Lo(r,l){return{...r,id:an(),order:l}}function Fo(r,l){const f=String(r||"").trim()||(l?Ke(l):"Annotated session");return/\bcopy$/i.test(f)?`${f} 2`:`${f} copy`}function Ho(r,l,f){if(l===f||l<0||f<0||l>=r.length||f>=r.length)return r;const p=[...r],[b]=p.splice(l,1);return b?(p.splice(f,0,b),ke(p)):r}function ae(r){return String(Math.round(I(r)*1e3)/10)}function Do(r){const l=Number.parseFloat(r);return Number.isFinite(l)?I(l/100):null}function Oo(r,l,f){return r.kind==="pin"||r.kind==="text-note"?l==="x"||l==="y"?{...r,[l]:I(f)}:r:r.kind==="box"?l==="x"||l==="y"?{...r,[l]:I(f)}:l==="width"||l==="height"?{...r,[l]:Se(f)}:r:l==="x"||l==="y"||l==="x2"||l==="y2"?{...r,[l]:I(f)}:r}function zo(r){return r.kind==="pin"||r.kind==="text-note"?[{key:"x",label:"X",value:ae(r.x)},{key:"y",label:"Y",value:ae(r.y)}]:r.kind==="box"?[{key:"x",label:"X",value:ae(r.x)},{key:"y",label:"Y",value:ae(r.y)},{key:"width",label:"Width",value:ae(r.width)},{key:"height",label:"Height",value:ae(r.height)}]:[{key:"x",label:"Start X",value:ae(r.x)},{key:"y",label:"Start Y",value:ae(r.y)},{key:"x2",label:"End X",value:ae(r.x2)},{key:"y2",label:"End Y",value:ae(r.y2)}]}function Uo(r){if(!r)return null;const l=Math.round(r.x*100),f=Math.round(r.y*100),p=Math.round(r.width*100),b=Math.round(r.height*100);return`crop ${l}%, ${f}% size ${p}% x ${b}%`}function Go(r){return Number.isFinite(r)?Math.min(4,Math.max(.25,Number(r.toFixed(2)))):1}function Va(r){const l=[`Annotated attachment: ${r.title||r.image.displayName}`,`Image: ${r.image.displayName}`,`Image Reference: ${r.image.referenceLabel||r.image.assetId}`,`Task ID: ${r.taskId}`,r.globalInstruction?`Global instruction: ${r.globalInstruction}`:"Global instruction: None provided.","Markers:"];return r.annotations.length===0?(l.push("0. No markers."),l.join(`
2
+ `)):(r.annotations.forEach((f,p)=>{const b=f.markerType||ce,x=Uo(f.cropHint);l.push(`${p+1}. ${f.kind} (${b})`),l.push(`Instruction: ${f.instruction||"No marker instruction."}`),x&&l.push(`Region: ${x}`)}),l.join(`
3
+ `))}function Et(r){return{title:r.title,globalInstruction:r.globalInstruction,annotations:ke(r.annotations)}}function qa(r){return JSON.stringify(Et(r))}function Ja(r){const l=JSON.parse(r);return Et({title:String(l?.title||""),globalInstruction:String(l?.globalInstruction||""),annotations:Array.isArray(l?.annotations)?l.annotations:[]})}function Ko(r,l){return Et({title:r?.title||l,globalInstruction:r?.globalInstruction||"",annotations:r?.annotations||[]})}function Qo({runtimeMode:r="local",apiBaseUrl:l="",cloudAuthBaseUrl:f="",workspaceId:p="default",sessionLoadReady:b=!0,requestedTarget:x=null,requestedSessionId:L=null,requestedOpenVersion:ne=0,imageTrayOpen:se,onCloseImageTray:dt,resolveTaskReferenceLabel:Ne,resolveImageReferenceLabel:We,onRequestedTargetHandled:Ce,onOpenTarget:G,onContextChange:ye,onBackToTask:je}){const $=r==="cloud"&&(f||l)||"",[ut,be]=s.useState(x),[mt,Te]=s.useState([]),[v,$e]=s.useState(null),[At,Ye]=s.useState(!1),[Re,Xe]=s.useState(""),[Pe,Ve]=s.useState(""),[j,re]=s.useState([]),[S,F]=s.useState(null),[ft,da]=s.useState(ct[ce]),[ht,pt]=s.useState(!1),[T,Be]=s.useState("select"),[sn,xe]=s.useState(!1),[gt,ua]=s.useState(!1),[V,q]=s.useState(!1),[ma,g]=s.useState(null),[K,R]=s.useState("saved"),[Wo,O]=s.useState(null),[yt,fa]=s.useState(!1),[oe,Mt]=s.useState(null),[de,Lt]=s.useState(!1),[rn,Ft]=s.useState(!1),[Ht,ha]=s.useState("json"),[bt,Dt]=s.useState(!1),[xt,Ot]=s.useState(!1),[on,pa]=s.useState(!1),[ln,vt]=s.useState(!1),[zt,_t]=s.useState(""),[qe,ga]=s.useState(!1),[Ut,cn]=s.useState([]),[It,dn]=s.useState(""),[un,ya]=s.useState(!1),[ba,xa]=s.useState(null),[ue,Je]=s.useState(!1),[Gt,Ze]=s.useState(!1),[P,Kt]=s.useState(1),[mn,Qe]=s.useState(!1),[fn,et]=s.useState(!1),[wt,va]=s.useState(!1),[kt,tt]=s.useState({width:0,height:0}),_a=s.useRef(null),Ee=s.useRef(null),Wt=s.useRef(null),Ia=s.useRef(null),me=s.useRef(L),ie=s.useRef(""),St=s.useRef(0),Yt=s.useRef(""),ve=s.useRef(0),Nt=s.useRef(null),wa=s.useRef(0),fe=s.useRef(!1),at=s.useRef(null),Xt=s.useRef(null),_e=s.useRef(null),H=s.useRef(""),A=s.useRef(""),Ct=s.useRef(null),J=s.useRef(null),Ae=s.useRef(!1),jt=s.useRef(null),nt=s.useRef(null),st=s.useRef(null),Ie=s.useRef(null),Me=s.useRef(null),Le=s.useRef(null),Fe=s.useRef(null),he=s.useRef(null),Z=s.useRef(null),ka=s.useRef(null),Sa=s.useRef(null),B=s.useMemo(()=>mt.find(e=>e.id===v)||null,[v,mt]),Q=s.useMemo(()=>`workspaceId=${encodeURIComponent(String(p||"default").trim()||"default")}`,[p]),E=s.useMemo(()=>({"x-taskforce-workspace-id":String(p||"default").trim()||"default"}),[p]),z=s.useMemo(()=>j.find(e=>e.id===S)||null,[j,S]),ee=s.useMemo(()=>Ge(x),[x]),le=typeof G=="function",d=le?x:ut,Tt=s.useMemo(()=>{const e=String(d?.taskId||"").trim();if(!e)return"";const n=Ne?.(e).trim()||"";if(n)return n;const o=String(d?.taskReferenceLabel||"").trim();return o&&o!==e?o:n||e},[d?.taskId,d?.taskReferenceLabel,Ne]),rt=s.useMemo(()=>{const e=String(d?.assetId||"").trim();if(!e)return"";const n=We?.(e).trim()||"";return n||String(d?.imageReferenceLabel||"").trim()},[d?.assetId,d?.imageReferenceLabel,We]),Vt=s.useMemo(()=>j.findIndex(e=>e.id===S),[j,S]),qt=z?.color||ft,Na=s.useMemo(()=>Et({title:Re,globalInstruction:Pe,annotations:j}),[j,Pe,Re]),He=s.useMemo(()=>qa(Na),[Na]),Ca=He!==A.current,y=s.useMemo(()=>({width:Math.max(kt.width*P,0),height:Math.max(kt.height*P,0)}),[kt.height,kt.width,P]),De=s.useMemo(()=>({visibleRadius:7,hitRadius:11}),[]),Jt=s.useMemo(()=>`0 0 ${Math.max(y.width,1)} ${Math.max(y.height,1)}`,[y.height,y.width]),ot=typeof se=="boolean",ja=s.useMemo(()=>{const e=It.trim().toLowerCase();return e?Ut.filter(n=>[n.displayName,n.originalFilename,n.imageReferenceLabel,n.taskReferenceLabel,n.assetId].filter(Boolean).join(" ").toLowerCase().includes(e)):Ut},[Ut,It]),Oe=s.useMemo(()=>{const e=Ee.current;return e?P>1||y.width>e.clientWidth+1||y.height>e.clientHeight+1:P>1},[y.height,y.width,P]),Zt=`${Math.round(P*100)}%`,W=Oe&&(mn||fn),Qt=s.useMemo(()=>{const e=new Map;return j.forEach((n,o)=>{e.set(n.id,o+1)}),e},[j]);s.useEffect(()=>{me.current=L},[L]),s.useEffect(()=>{const e=Ia.current;if(!e||!S)return;e.focus();const n=e.value.length;e.setSelectionRange(n,n)},[S]),s.useEffect(()=>{_e.current=v},[v]),s.useEffect(()=>{Ye(!1)},[v]),s.useEffect(()=>{if(!z){pt(!1);return}da(z.color||ct[z.markerType||ce])},[z]);const $t=s.useCallback(async e=>{const n=typeof performance<"u"?performance.now():Date.now(),o=await ge(`/api/taskforce/annotated-attachments/sessions?${Q}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...E},body:JSON.stringify({taskId:e.taskId,baseImageAssetId:e.assetId,title:Ke(e),globalInstruction:"",annotations:[]})},$),i=await o.json().catch(()=>({}));if(!o.ok)throw new Error(String(i?.error||"Failed to create session."));const c=i?.session;if(!c?.id)throw new Error("Failed to create session.");return Rt("annotated_session_create_completed",{assetId:e.assetId,taskId:e.taskId||null,sessionId:c.id,durationMs:Math.round((typeof performance<"u"?performance.now():Date.now())-n),debugTimings:i?.debugTimings||null,serverTiming:typeof o.headers?.get=="function"&&o.headers.get("server-timing")||null}),c},[E,$,Q]),Ta=s.useCallback(e=>new Promise((n,o)=>{const i=new FileReader;i.onload=()=>n(typeof i.result=="string"?i.result:""),i.onerror=()=>o(i.error||new Error("Failed to read clipboard image.")),i.readAsDataURL(e)}),[]),N=s.useCallback(()=>{at.current!==null&&(window.clearTimeout(at.current),at.current=null),st.current!==null&&(window.clearTimeout(st.current),st.current=null)},[]),ze=s.useCallback(e=>{Xe(e.title),Ve(e.globalInstruction),re(e.annotations),F(n=>n&&e.annotations.some(o=>o.id===n)?n:e.annotations[0]?.id||null)},[]),pe=s.useCallback((e,n)=>{const o=Ke(n||d||{assetId:e.baseImageAssetId,displayName:"Annotated session"}),i=Ko(e,o),c=qa(i);fe.current=!0,N(),ze(i),A.current=c,H.current=c,Ct.current=null,Ae.current=!1,jt.current=null,nt.current=null,O(null),R("saved"),q(!1)},[d,ze,N]),it=s.useCallback(e=>{fe.current=!0,N(),Te([]),$e(null),xe(!1),Ye(!1),Xe(Ke(e)),Ve(""),re([]),F(null),A.current="",H.current="",Ct.current=null,Ae.current=!1,jt.current=null,nt.current=null,Mt(null),Ft(!1),O(null),R("saved"),q(!1)},[N]),we=s.useCallback(async(e,n,o)=>{const i=_e.current;if(!i)return!0;if(e===A.current)return J.current||(O(null),R("saved")),!0;if(J.current){if(Ae.current=!0,!o?.waitForInFlight||!await J.current)return!1;const m=H.current;return m===A.current?!0:we(m,n,o)}N();const c=Ja(e);jt.current=i,Ct.current=e,q(!0),g(null),O(null),R("saving");const h=(async()=>{try{const u=await ge(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(i)}?${Q}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json",...E},body:JSON.stringify(c)},$),m=await u.json().catch(()=>({}));if(!u.ok)throw new Error(String(m?.error||"Failed to save session."));const _=m?.session;if(!_?.id)throw new Error("Failed to save session.");Te(ta=>{const Da=ta.findIndex(Ln=>Ln.id===_.id);if(Da===-1)return[_,...ta];const Oa=[...ta];return Oa[Da]=_,Oa}),A.current=e,nt.current=null,O(null);const k=_e.current===_.id,C=H.current,X=C!==e,lt=Ae.current||X;return Ae.current=!1,k&&!lt?(fe.current=!0,ze(c),R("saved")):!lt&&C===A.current?R("saved"):R("pending"),!0}catch(u){const m=u instanceof Error?u.message:"Failed to save session.";return g(m),O(m),R("error"),nt.current!==e&&_e.current===i&&H.current===e&&(nt.current=e,st.current=window.setTimeout(()=>{st.current=null,!(_e.current!==i||H.current!==e)&&we(e,n,{waitForInFlight:!0})},1500)),!1}finally{Ct.current=null,J.current=null,jt.current=null,q(!1)}})();J.current=h;const w=await h;if(w){const u=H.current;if(u!==A.current)return we(u,n,o)}return w},[ze,N,E,$,Q]),$a=s.useCallback(e=>{if(_e.current){if(H.current===A.current){O(null),J.current||R("saved");return}K!=="saving"&&R("pending"),N(),at.current=window.setTimeout(()=>{at.current=null,we(H.current,"structure")},e)}},[N,we,K]),Y=s.useCallback(e=>{Xt.current=e},[]),M=s.useCallback(async e=>{N();const n=H.current;return!_e.current||n===A.current?(O(null),J.current||R("saved"),!0):we(n,e,{waitForInFlight:!0})},[N,we]),hn=s.useCallback(()=>{const e=A.current;e&&(N(),fe.current=!0,ze(Ja(e)),O(null),R("saved"),g(null))},[ze,N]),U=s.useCallback(async(e,n)=>{const o=typeof performance<"u"?performance.now():Date.now(),i=JSON.stringify({targetKey:Ge(e),requestedSessionId:n?.requestedSessionId??null,autoCreateIfEmpty:n?.autoCreateIfEmpty===!0});if(Nt.current===i)return;const c=ve.current+1;ve.current=c,Nt.current=i,ua(!0),g(null);try{const h=new URLSearchParams;p&&h.set("workspaceId",p),e.taskId&&h.set("taskId",e.taskId),h.set("imageAssetId",e.assetId);const w=await ge(`/api/taskforce/annotated-attachments/sessions?${h.toString()}`,{credentials:"include",headers:E,cache:"no-store"},$),u=await w.json().catch(()=>({}));if(!w.ok)throw new Error(String(u?.error||"Failed to load annotated attachment sessions."));const m=Array.isArray(u?.sessions)?u.sessions:[];if(Rt("annotated_sessions_loaded",{assetId:e.assetId,taskId:e.taskId||null,requestedSessionId:n?.requestedSessionId??null,autoCreateIfEmpty:n?.autoCreateIfEmpty===!0,sessionCount:m.length,durationMs:Math.round((typeof performance<"u"?performance.now():Date.now())-o),serverTiming:typeof w.headers?.get=="function"&&w.headers.get("server-timing")||null}),m.length===0&&n?.autoCreateIfEmpty&&String(e.assetId||"").trim()){const C=n?.requestedSessionId??me.current;if(me.current=null,C&&g("The previously selected annotation session could not be restored."),ve.current!==c)return;const X=await $t(e);if(Rt("annotated_sessions_auto_created_after_empty_load",{assetId:e.assetId,taskId:e.taskId||null,sessionId:X.id,totalDurationMs:Math.round((typeof performance<"u"?performance.now():Date.now())-o)}),ve.current!==c)return;Te([X]),$e(X.id),xe(!1),pe(X,e);return}if(ve.current!==c)return;Te(m),xe(m.length===0);const _=n?.requestedSessionId??me.current;me.current=null;const k=m.find(C=>C.id===_)||m[0]||null;_&&!k&&g("The previously selected annotation session could not be restored."),$e(k?.id||null),k?pe(k,e):(fe.current=!0,N(),Xe(Ke(e)),Ve(""),re([]),F(null),A.current="",H.current="",O(null),R("saved"))}catch(h){if(ve.current!==c)return;g(h instanceof Error?h.message:"Failed to load sessions.")}finally{Nt.current===i&&(Nt.current=null),ve.current===c&&ua(!1)}},[N,$t,E,$,pe,p]),Ra=s.useCallback(async()=>{ya(!0),xa(null);try{const e=new URLSearchParams;e.set("workspaceId",String(p||"default").trim()||"default");const n=await ge(`/api/taskforce/annotated-attachments/images?${e.toString()}`,{credentials:"include",headers:E,cache:"no-store"},$),o=await n.json().catch(()=>({}));if(!n.ok)throw new Error(String(o?.error||"Failed to load images."));cn(Array.isArray(o?.images)?o.images:[])}catch(e){xa(e instanceof Error?e.message:"Failed to load images.")}finally{ya(!1)}},[E,$,p]),pn=s.useCallback(async()=>{if(typeof navigator>"u"||!navigator.clipboard||typeof navigator.clipboard.read!="function"){g("Clipboard image paste is not supported in this environment.");return}va(!0),g(null),wa.current=Date.now()+2e3;try{const n=(await navigator.clipboard.read()).find(_=>_.types.some(k=>k.startsWith("image/"))),o=n?.types.find(_=>_.startsWith("image/"))||"";if(!n||!o)throw new Error("No image found on the clipboard.");const i=await n.getType(o),c=await Ta(i);if(!c)throw new Error("Failed to read clipboard image.");const h=o==="image/jpeg"?"jpg":o==="image/webp"?"webp":o==="image/gif"?"gif":"png",w=await fetch("/api/taskforce/context-upload",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...E},body:JSON.stringify({file:c,originalName:`pasted-image.${h}`,workspaceId:String(p||"default").trim()||"default"})}),u=await w.json().catch(()=>({}));if(!w.ok||!u?.success||typeof u?.assetId!="string"||typeof u?.path!="string")throw new Error(String(u?.error||"Failed to paste image into Image Notes."));const m={assetId:u.assetId,imageReferenceLabel:typeof u?.referenceLabel=="string"?u.referenceLabel:void 0,path:u.path,displayName:typeof u?.displayName=="string"&&u.displayName.trim().length>0?u.displayName.trim():"Pasted image"};me.current=null,le?(ie.current="",G?.(m,{sessionId:null})):(be(m),ie.current=Ge(m),U(m,{autoCreateIfEmpty:!0}))}catch(e){g(e instanceof Error?e.message:"Failed to paste image.")}finally{va(!1)}},[le,U,G,Ta,E,p]),gn=s.useCallback(async()=>{const e=zt.trim();if(!e){g("Enter an image reference to open.");return}ga(!0),g(null);try{const n=new URLSearchParams({workspaceId:String(p||"default").trim()||"default"}),o=await ge(`/api/taskforce/annotated-attachments/images/${encodeURIComponent(e)}?${n.toString()}`,{method:"GET",credentials:"include",headers:E}),i=await o.json().catch(()=>({}));if(!o.ok||!i?.target||typeof i.target.assetId!="string"||typeof i.target.path!="string")throw new Error(String(i?.error||"Failed to open image reference."));const c={assetId:i.target.assetId,path:i.target.path,displayName:typeof i.target.displayName=="string"&&i.target.displayName.trim().length>0?i.target.displayName.trim():"Image attachment",taskId:typeof i.target.taskId=="string"&&i.target.taskId.trim().length>0?i.target.taskId.trim():void 0,taskReferenceLabel:typeof i.target.taskReferenceLabel=="string"&&i.target.taskReferenceLabel.trim().length>0?i.target.taskReferenceLabel.trim():void 0,imageReferenceLabel:typeof i.target.imageReferenceLabel=="string"&&i.target.imageReferenceLabel.trim().length>0?i.target.imageReferenceLabel.trim():void 0};me.current=null,vt(!1),_t(""),le?(ie.current="",G?.(c,{sessionId:null})):(be(c),ie.current=Ge(c),U(c,{autoCreateIfEmpty:!0}))}catch(n){g(n instanceof Error?n.message:"Failed to open image reference.")}finally{ga(!1)}},[le,U,G,zt,E,p]),yn=s.useCallback(async e=>{if(!e.assetId||!e.path||!await M("session-switch"))return;const o={assetId:e.assetId,path:e.path,displayName:String(e.displayName||e.originalFilename||"Image attachment").trim()||"Image attachment",...e.taskId?{taskId:e.taskId}:{},...e.taskReferenceLabel?{taskReferenceLabel:e.taskReferenceLabel}:{},...e.imageReferenceLabel?{imageReferenceLabel:e.imageReferenceLabel}:{}};me.current=null,g(null),le?(ie.current="",G?.(o,{sessionId:null})):(be(o),ie.current=Ge(o),it(o),U(o,{autoCreateIfEmpty:!0}))},[M,le,U,G,it]),Pa=s.useCallback(e=>{Te(n=>{const o=n.findIndex(c=>c.id===e.id);if(o===-1)return[e,...n];const i=[...n];return i[o]=e,i}),$e(e.id),pe(e,d)},[d,pe]),Ba=s.useCallback(e=>{Te(n=>{const o=n.filter(c=>c.id!==e),i=o[0]||null;return $e(i?.id||null),i?pe(i,d):(fe.current=!0,N(),Xe(Ke(d||{displayName:"Annotated session"})),Ve(""),re([]),F(null),A.current="",H.current="",O(null),R("saved")),o})},[d,N,pe]);s.useEffect(()=>{if(!x)return;if(le||be(n=>n&&Ge(n)===ee&&n.taskReferenceLabel===x.taskReferenceLabel&&n.imageReferenceLabel===x.imageReferenceLabel&&n.displayName===x.displayName?n:x),!b){ee&&(ee!==ie.current||ne!==St.current)&&ee!==Yt.current&&(it(x),Yt.current=ee,St.current=ne,Rt("annotated_sessions_load_deferred",{assetId:x.assetId,taskId:x.taskId||null,requestedTargetKey:ee})),Ce?.();return}ee&&(ee!==ie.current||ne!==St.current)&&(it(x),ie.current=ee,St.current=ne,Yt.current="",U(x,{autoCreateIfEmpty:!0})),Ce?.()},[le,U,Ce,ne,x,ee,it,L,b]),s.useEffect(()=>{d&&ye?.({target:d,sessionId:v})},[d,ye,v]),s.useEffect(()=>{if(!d||typeof document>"u"||!b)return;const e=()=>{Date.now()<wa.current||H.current!==A.current||V||gt||U(d,{requestedSessionId:v})},n=()=>{document.visibilityState==="visible"&&e()};return document.addEventListener("visibilitychange",n),()=>{document.removeEventListener("visibilitychange",n)}},[d,U,gt,V,v,b]),s.useEffect(()=>{if(!d?.path){Je(!1),Ze(!1),tt({width:0,height:0});return}Je(!0),Ze(!1),tt({width:0,height:0}),Kt(1),Qe(!1),et(!1)},[d?.path]),s.useEffect(()=>{if(!ue)return;const e=Wt.current;!e||!e.complete||e.naturalWidth<=0||e.naturalHeight<=0||(tt({width:e.naturalWidth,height:e.naturalHeight}),Je(!1),Ze(!1))},[d?.path,ue]),s.useEffect(()=>{Oe||(Qe(!1),et(!1))},[Oe]),s.useEffect(()=>{Mt(null)},[v]),s.useEffect(()=>{if(H.current=He,fe.current){fe.current=!1;return}if(!v){N(),O(null),R("saved");return}if(He===A.current){N(),J.current||(O(null),R("saved"));return}const e=Xt.current;if(Xt.current=null,K==="error"&&e===null)return;const n=e??600;if(J.current){Ae.current=!0,R("pending");return}$a(n)},[N,He,K,$a,v]),s.useEffect(()=>{v&&(J.current||He===A.current&&K!=="error"&&K!=="saved"&&(O(null),R("saved")))},[He,K,v]),s.useEffect(()=>{!ot||!se||Ra()},[Ra,se,ot]),s.useEffect(()=>{if(typeof window>"u")return;const e=n=>{H.current!==A.current&&(n.preventDefault(),n.returnValue="")};return window.addEventListener("beforeunload",e),()=>window.removeEventListener("beforeunload",e)},[]),s.useEffect(()=>{if(typeof document>"u")return;const e=()=>{document.visibilityState==="hidden"&&M("visibility-hidden")};return document.addEventListener("visibilitychange",e),()=>document.removeEventListener("visibilitychange",e)},[M]),s.useEffect(()=>{if(!ht||typeof document>"u")return;const e=n=>{const o=n.target;o instanceof Node&&(ka.current?.contains(o)||pt(!1))};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[ht]),s.useEffect(()=>{if(!At||typeof document>"u")return;const e=n=>{const o=n.target;o instanceof Node&&(Sa.current?.contains(o)||Ye(!1))};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[At]),s.useEffect(()=>()=>{N()},[N]),s.useEffect(()=>{if(!de)return;const e=window.setTimeout(()=>Lt(!1),2e3);return()=>window.clearTimeout(e)},[de]),s.useEffect(()=>{if(!bt)return;const e=window.setTimeout(()=>Dt(!1),2e3);return()=>window.clearTimeout(e)},[bt]),s.useEffect(()=>{if(!xt)return;const e=window.setTimeout(()=>Ot(!1),2e3);return()=>window.clearTimeout(e)},[xt]);const te=s.useCallback((e,n,o=300)=>{re(i=>ke(i.map(c=>c.id===e?n(c):c))),Y(o)},[Y]),bn=s.useCallback(e=>{z&&(te(z.id,n=>({...n,color:e})),da(e),pt(!1))},[z,te]),Ea=s.useCallback(async()=>{if(!(!d||!await M("session-switch"))){q(!0),g(null);try{const n=await $t(d);xe(!1),await U(d,{requestedSessionId:n.id,autoCreateIfEmpty:!1}),Be("select")}catch(n){g(n instanceof Error?n.message:"Failed to create session."),xe(!0)}finally{q(!1)}}},[d,$t,M,U]),xn=s.useCallback(e=>{if(e!=="select"&&!B){Qe(!1),g("No session exists for this image yet."),xe(!0);return}g(null),xe(!1),Qe(!1),Be(e)},[B]),vn=s.useCallback(async()=>{if(!(!d||!B||!await M("duplicate"))){q(!0),g(null);try{const n=ke(j.map((h,w)=>Lo(h,w))),o=await ge(`/api/taskforce/annotated-attachments/sessions?${Q}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",...E},body:JSON.stringify({taskId:d.taskId,baseImageAssetId:d.assetId,title:Fo(Re||B.title,d),globalInstruction:Pe,annotations:n})},$),i=await o.json().catch(()=>({}));if(!o.ok)throw new Error(String(i?.error||"Failed to duplicate session."));const c=i?.session;if(!c?.id)throw new Error("Failed to duplicate session.");Pa(c),Be("select")}catch(n){g(n instanceof Error?n.message:"Failed to duplicate session.")}finally{q(!1)}}},[d,j,Pe,Re,Pa,M,E,$,B,Q]),_n=s.useCallback(async()=>M("manual"),[M]),Aa=s.useCallback(async()=>{if(v){fa(!0),g(null);try{const e=await ge(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(v)}/payload?${Q}`,{credentials:"include",headers:E},$),n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(String(n?.error||"Failed to load payload preview."));Mt(n?.payload||null)}catch(e){g(e instanceof Error?e.message:"Failed to load payload preview.")}finally{fa(!1)}}},[E,$,v,Q]),In=s.useCallback(()=>{v&&M("payload-preview").then(e=>{e&&(Ft(!0),Aa())})},[M,Aa,v]),Ma=s.useCallback(async e=>{if(!oe||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){g("Clipboard copy is not available in this browser.");return}try{const n=e==="json"?JSON.stringify(oe,null,2):Va(oe);await navigator.clipboard.writeText(n),Lt(e)}catch(n){g(n instanceof Error?n.message:"Failed to copy payload content."),Lt(!1)}},[oe]),wn=s.useCallback(async()=>{if(!Tt||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){g("Clipboard copy is not available in this browser.");return}try{await navigator.clipboard.writeText(Tt),Dt(!0)}catch(e){g(e instanceof Error?e.message:"Failed to copy task id."),Dt(!1)}},[Tt]),kn=s.useCallback(async()=>{if(!rt||!navigator.clipboard||typeof navigator.clipboard.writeText!="function"){g("Clipboard copy is not available in this browser.");return}try{await navigator.clipboard.writeText(rt),Ot(!0)}catch(e){g(e instanceof Error?e.message:"Failed to copy image reference."),Ot(!1)}},[rt]),Sn=s.useCallback(async()=>{if(!v||!await M("delete-session"))return;const n=(B?.title||"Untitled session").trim()||"Untitled session";if(window.confirm(`Delete the annotated attachment session "${n}"?`)){q(!0),g(null);try{const o=await ge(`/api/taskforce/annotated-attachments/sessions/${encodeURIComponent(v)}?${Q}`,{method:"DELETE",credentials:"include",headers:E},$),i=await o.json().catch(()=>({}));if(!o.ok)throw new Error(String(i?.error||"Failed to delete session."));Ba(v),Be("select")}catch(o){g(o instanceof Error?o.message:"Failed to delete session.")}finally{q(!1)}}},[M,Ba,E,$,B,v,Q]),ea=s.useCallback(()=>{S&&(re(e=>ke(e.filter(n=>n.id!==S))),F(null),Y(300))},[Y,S]),Ue=s.useCallback(e=>{const n=Go(e),o=Ee.current;if(!o||n===P){Kt(n);return}const i=(o.scrollLeft+o.clientWidth/2)*(n/P)-o.clientWidth/2,c=(o.scrollTop+o.clientHeight/2)*(n/P)-o.clientHeight/2;Kt(n),window.requestAnimationFrame(()=>{o.scrollLeft=Math.max(0,i),o.scrollTop=Math.max(0,c)})},[P]),Nn=s.useCallback(()=>{Ue(P+.25)},[Ue,P]),Cn=s.useCallback(()=>{Ue(P-.25)},[Ue,P]),jn=s.useCallback(()=>{Ue(1);const e=Ee.current;e&&window.requestAnimationFrame(()=>{e.scrollLeft=0,e.scrollTop=0})},[Ue]),La=s.useCallback(e=>{S&&re(n=>{const o=n.findIndex(c=>c.id===S);if(o===-1)return n;const i=e==="up"?o-1:o+1;return i<0||i>=n.length?n:(Y(300),Ho(n,o,i))})},[Y,S]);s.useEffect(()=>{if(!S)return;const e=n=>{n.key==="Delete"&&(nn(n.target)||(n.preventDefault(),ea()))};return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[ea,S]),s.useEffect(()=>{const e=i=>{i.code==="Space"&&Oe&&(Ro(i.target)||(i.preventDefault(),et(!0)))},n=i=>{i.code==="Space"&&et(!1)},o=()=>{et(!1)};return window.addEventListener("keydown",e),window.addEventListener("keyup",n),window.addEventListener("blur",o),()=>{window.removeEventListener("keydown",e),window.removeEventListener("keyup",n),window.removeEventListener("blur",o)}},[Oe]);const D=s.useCallback(e=>{const n=_a.current?.getBoundingClientRect();return!n||n.width<=0||n.height<=0?null:{x:I((e.clientX-n.left)/n.width),y:I((e.clientY-n.top)/n.height)}},[]),Tn=s.useCallback(e=>{if(W){const i=Ee.current;if(!i)return;Z.current={pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,scrollLeft:i.scrollLeft,scrollTop:i.scrollTop},e.currentTarget.setPointerCapture(e.pointerId),e.preventDefault();return}if(!v)return;if(T==="select"){F(null);return}const n=D(e);if(!n)return;if(T==="pin"||T==="text-note"){const i=Xa(T,n,n,ft);re(c=>ke([...c,i])),F(i.id),Y(300),Be("select");return}Ie.current=n;const o=Xa(T,n,n,ft);he.current={annotationId:o.id,kind:T},e.currentTarget.setPointerCapture?.(e.pointerId),re(i=>ke([...i,o])),F(o.id),Y(300)},[ft,W,Y,D,v,T]),$n=s.useCallback(e=>{if(Z.current?.pointerId===e.pointerId){e.currentTarget.releasePointerCapture?.(e.pointerId);return}if(!Ie.current||!he.current||T!=="box"&&T!=="arrow")return;const n=D(e);Ie.current=null,he.current=null,e.currentTarget.releasePointerCapture?.(e.pointerId),n&&Be("select")},[D,T]),Fa=s.useCallback((e,n)=>{if(T!=="select"||W)return;const o=D(e);o&&(Me.current={annotationId:n.id,originPointer:o,originAnnotation:n},F(n.id),e.stopPropagation())},[W,D,T]),Rn=s.useCallback(e=>{if(Z.current?.pointerId===e.pointerId){const u=Ee.current;if(!u)return;const m=e.clientX-Z.current.startX,_=e.clientY-Z.current.startY;u.scrollLeft=Z.current.scrollLeft-m,u.scrollTop=Z.current.scrollTop-_;return}if(he.current&&Ie.current){const u=D(e);if(!u)return;const{annotationId:m,kind:_}=he.current,k=Ie.current;te(m,C=>_==="box"&&C.kind==="box"?{...C,x:I(Math.min(k.x,u.x)),y:I(Math.min(k.y,u.y)),width:Se(Math.abs(u.x-k.x)),height:Se(Math.abs(u.y-k.y))}:_==="arrow"&&C.kind==="arrow"?{...C,x:k.x,y:k.y,x2:u.x,y2:u.y}:C);return}if(Le.current){const u=D(e);if(!u)return;const{annotationId:m,originPointer:_,originAnnotation:k}=Le.current,C=u.x-_.x,X=u.y-_.y;te(m,()=>({...k,width:Se(k.width+C),height:Se(k.height+X)}));return}if(Fe.current){const u=D(e);if(!u)return;const{annotationId:m,endpoint:_,originPointer:k,originAnnotation:C}=Fe.current,X=u.x-k.x,lt=u.y-k.y;te(m,()=>_==="tail"?{...C,x:I(C.x+X),y:I(C.y+lt)}:{...C,x2:I(C.x2+X),y2:I(C.y2+lt)});return}if(!Me.current)return;const n=D(e);if(!n)return;const{annotationId:o,originPointer:i,originAnnotation:c}=Me.current,h=n.x-i.x,w=n.y-i.y;te(o,()=>c.kind==="pin"||c.kind==="text-note"?{...c,x:I(c.x+h),y:I(c.y+w)}:c.kind==="box"?{...c,x:I(c.x+h),y:I(c.y+w)}:{...c,x:I(c.x+h),y:I(c.y+w),x2:I(c.x2+h),y2:I(c.y2+w)})},[D,te]),Pn=s.useCallback(()=>{Ie.current=null,Me.current=null,Le.current=null,Fe.current=null,he.current=null,Z.current=null},[]),Bn=s.useCallback(()=>{Ie.current=null,Me.current=null,Le.current=null,Fe.current=null,he.current=null,Z.current=null},[]),En=s.useCallback(()=>{Me.current=null,Le.current=null,Fe.current=null,he.current=null,Z.current=null},[]),An=s.useCallback((e,n)=>{if(T!=="select"||W)return;const o=D(e);o&&(Le.current={annotationId:n.id,originPointer:o,originAnnotation:n},F(n.id),e.stopPropagation())},[W,D,T]),Ha=s.useCallback((e,n,o)=>{if(T!=="select"||W)return;const i=D(e);i&&(Fe.current={annotationId:n.id,originPointer:i,originAnnotation:n,endpoint:o},F(n.id),e.stopPropagation())},[W,D,T]),Mn=B?.updatedAt?ra(B.updatedAt):"Not saved yet";return t.jsxs("div",{className:`${a.shell} ${ot?a.shellWithImageTray:""} ${ot&&se?a.shellImageTrayOpen:""}`.trim(),children:[ot?t.jsxs("aside",{className:`${a.imageTrayPanel} ${se?a.imageTrayPanelOpen:""}`.trim(),"aria-label":"Image tray","aria-hidden":!se,children:[t.jsxs("div",{className:a.imageTrayHeader,children:[t.jsxs("span",{className:a.imageTrayTitle,children:[t.jsx(Ua,{size:14}),"Images"]}),t.jsx("button",{type:"button",className:"tf-control-icon",onClick:dt,title:"Collapse image tray","aria-label":"Collapse image tray",children:t.jsx(Dn,{size:16})})]}),t.jsxs("div",{className:a.imageTraySearch,children:[t.jsx(On,{size:12,className:a.imageTraySearchIcon}),t.jsx("input",{type:"text",placeholder:"Search images...",value:It,onChange:e=>dn(e.target.value),className:a.imageTraySearchInput})]}),t.jsx("div",{className:`tf-scrollbar ${a.imageTrayList}`,children:un?t.jsx("div",{className:a.imageTrayState,children:"Loading images..."}):ba?t.jsx("div",{className:a.imageTrayStateError,children:ba}):ja.length===0?t.jsx("div",{className:a.imageTrayState,children:It.trim()?"No images match your search.":"No images found."}):ja.map(e=>{const n=d?.assetId===e.assetId,o=typeof e.attachmentCount=="number"&&Number.isFinite(e.attachmentCount)?Math.max(0,e.attachmentCount):0,i=typeof e.sessionCount=="number"&&Number.isFinite(e.sessionCount)?Math.max(0,e.sessionCount):0,c=o>1?`${o} tasks linked`:o===1?e.taskReferenceLabel||"1 task linked":"Unattached",h=String(e.displayName||e.originalFilename||"Image attachment").trim()||"Image attachment";return t.jsxs("button",{type:"button",className:`${a.imageTrayItem} ${n?a.imageTrayItemActive:""}`.trim(),onClick:()=>{yn(e)},title:h,children:[t.jsx("span",{className:a.imageTrayThumb,children:t.jsx("img",{src:e.path,alt:"",loading:"lazy"})}),t.jsxs("span",{className:a.imageTrayItemBody,children:[t.jsx("span",{className:a.imageTrayItemTitle,children:h}),t.jsx("span",{className:a.imageTrayItemTask,children:c}),t.jsxs("span",{className:a.imageTrayItemMeta,children:[t.jsxs("span",{className:a.imageTrayItemMetaDetails,children:[Po(e.sizeBytes),e.updatedAt?t.jsxs(t.Fragment,{children:[" · ",Bo(e.updatedAt)]}):null,i>0?t.jsxs(t.Fragment,{children:[" · ",i," session",i===1?"":"s"]}):null]}),e.imageReferenceLabel?t.jsx("span",{className:a.imageTrayItemReference,children:e.imageReferenceLabel}):null]})]})]},e.assetId)})})]}):null,t.jsxs("section",{className:`tf-surface-panel ${a.panel} ${a.sessionPanel}`,children:[t.jsx("div",{className:a.sessionContextBar,children:d?.taskId?t.jsxs(t.Fragment,{children:[t.jsx("div",{className:a.sessionContextLeft,children:je?t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.backToTaskBtn}`,onClick:()=>{M("back-to-task").then(e=>{e&&d.taskId&&je(d.taskId)})},"aria-label":"Back to task",title:"Back to task",children:t.jsx(zn,{size:16})}):t.jsx("div",{className:a.sessionContextSpacer,"aria-hidden":"true"})}),t.jsx("div",{className:a.sessionContextRight,children:t.jsx(Hn,{copied:bt,onClick:()=>{wn()},title:"Copy task id",ariaLabel:bt?"Copied task id":"Copy task id",label:Tt})})]}):t.jsxs(t.Fragment,{children:[t.jsx("div",{className:a.sessionContextLeft,children:t.jsx("span",{className:`tf-label-micro ${a.sessionContextLabel}`,children:"Unattached Image"})}),t.jsx("div",{className:a.sessionContextRight,children:t.jsx("div",{className:a.sessionContextSpacer,"aria-hidden":"true"})})]})}),t.jsxs("div",{className:a.panelHeader,children:[t.jsxs("div",{className:a.panelHeaderText,children:[t.jsx("div",{className:`tf-heading-card ${a.panelTitle}`,children:"Sessions"}),d?null:t.jsx("div",{className:"tf-text-secondary",children:"Open an image attachment to begin"})]}),t.jsx("div",{className:a.sessionActions,children:t.jsx("button",{type:"button",className:`tf-control-icon ${a.iconButton}`,onClick:()=>{Ea()},disabled:!d||V,"aria-label":"Create session",title:"Create session",children:t.jsx(Un,{size:16})})})]}),d?gt?t.jsx("div",{className:a.emptyState,children:t.jsx("p",{className:"tf-empty-copy",children:"Loading sessions…"})}):t.jsx("div",{className:`tf-scrollbar ${a.sessionList}`,children:mt.length===0?t.jsxs("div",{className:a.sessionEmptyState,children:[t.jsx("div",{className:`tf-heading-card ${a.sessionTitle}`,children:"No sessions yet"}),t.jsx("div",{className:`tf-text-secondary ${a.annotationSummary}`,children:"Create the first annotation session for this image."})]}):mt.map(e=>{const n=v===e.id,o=n&&At,i=Eo(n?Pe:e.globalInstruction),c=(n?Re:e.title)||"Untitled session";return t.jsxs("div",{className:`tf-surface-elevated ${a.sessionCard} ${n?a.sessionCardActive:""}`,ref:n?Sa:void 0,onBlur:o?h=>{const w=h.relatedTarget;w instanceof Node&&h.currentTarget.contains(w)||Ye(!1)}:void 0,children:[o?t.jsxs("div",{className:a.sessionCardBody,children:[t.jsxs("div",{className:a.sessionMeta,children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-session-title",children:"Session title"}),t.jsx("input",{id:"annotated-session-title",className:`tf-field-shell ${a.textInput} ${a.sessionTitleInput}`,value:Re,onChange:h=>{Xe(h.target.value),Y(600)},placeholder:"Session title"}),t.jsx("span",{className:`tf-text-meta ${a.sessionTimestamp}`,children:ra(e.updatedAt)})]}),Bt(e)?t.jsxs("div",{className:`tf-text-secondary ${a.sessionCreator}`,children:["Created by ",Bt(e)]}):null,t.jsxs("div",{className:a.sessionInstructionEditor,children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-session-instruction",children:"Session instruction"}),t.jsx("textarea",{id:"annotated-session-instruction",className:`tf-field-shell ${a.textArea} ${a.sessionInstructionField}`,value:Pe,onChange:h=>{Ve(h.target.value),Y(600)},placeholder:"Add overall instructions, context, or framing for this session."})]}),t.jsxs("div",{className:`tf-text-secondary ${a.annotationSummary}`,children:[e.annotations.length," annotation",e.annotations.length===1?"":"s"]})]}):t.jsxs("button",{type:"button",className:a.sessionCardButton,onClick:()=>{if(n){Ye(!0);return}M("session-switch").then(h=>{h&&($e(e.id),pe(e,d))})},"aria-pressed":n,children:[t.jsxs("div",{className:a.sessionMeta,children:[t.jsx("span",{className:`tf-heading-card ${a.sessionTitle}`,children:c}),t.jsx("span",{className:`tf-text-meta ${a.sessionTimestamp}`,children:ra(e.updatedAt)})]}),Bt(e)?t.jsxs("div",{className:`tf-text-secondary ${a.sessionCreator}`,children:["Created by ",Bt(e)]}):null,i?t.jsx("div",{className:`tf-text-secondary ${a.sessionInstructionPreview}`,children:i}):null,t.jsxs("div",{className:`tf-text-secondary ${a.annotationSummary}`,children:[e.annotations.length," annotation",e.annotations.length===1?"":"s"]})]}),n?t.jsx("div",{className:a.sessionCardFooter,children:t.jsxs(t.Fragment,{children:[t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${a.iconButton}`,onClick:()=>{vn()},disabled:V,"aria-label":"Duplicate session",title:`Duplicate session "${c}"`,children:t.jsx(sa,{size:16})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${a.iconButton}`,onClick:()=>{Sn()},disabled:V,"aria-label":"Delete session",title:`Delete session "${c}"`,children:t.jsx(Ga,{size:16})})]})}):null]},e.id)})}):t.jsxs("div",{className:a.emptyState,children:[t.jsx("strong",{className:"tf-empty-title",children:"No image selected"}),t.jsx("p",{className:"tf-empty-copy",children:"Open an image attachment from a task to start an annotated session."})]})]}),t.jsxs("section",{className:`tf-surface-panel ${a.panel} ${a.canvasPanel}`,children:[t.jsxs("div",{className:a.panelHeader,children:[t.jsxs("div",{className:a.canvasHeading,children:[t.jsx("div",{className:"tf-heading-card",children:d?.displayName||"Annotated attachment"}),t.jsx("div",{className:"tf-text-secondary",children:B?`${j.length} markers · ${Mn}`:"Pick or create a session"})]}),rt?t.jsx(Qn,{copied:xt,onClick:()=>{kn()},title:"Copy image reference",ariaLabel:xt?"Copied image reference":"Copy image reference",label:rt}):null]}),t.jsxs("div",{className:a.toolRail,children:[t.jsxs("div",{className:`${a.toolbarCluster} ${a.toolbarViewportCluster}`,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>{_n()},disabled:!B||V||!Ca&&K!=="error","aria-label":V?"Saving session":"Save session",title:V?"Saving session":"Save session",children:t.jsx(Gn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>vt(!0),disabled:qe,"aria-label":"Open image",title:"Open image",children:t.jsx(Ua,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>{pn()},disabled:wt,"aria-label":wt?"Pasting image":"Paste image",title:wt?"Pasting image":"Paste image",children:wt?t.jsx(Ka,{size:18,className:za.spin}):t.jsx(Kn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>{B&&hn()},disabled:!B||!Ca,"aria-label":"Reset unsaved changes",title:"Reset unsaved changes",children:t.jsx(Wn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${a.iconButton}`,onClick:Cn,disabled:!d||ue||P<=.25,"aria-label":"Zoom out",children:t.jsx(Yn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:jn,disabled:!d||ue||P===1,"aria-label":`Reset zoom to 100 percent (currently ${Zt})`,title:`Reset zoom to 100% (currently ${Zt})`,children:t.jsx("span",{children:Zt})}),t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${a.iconButton}`,onClick:Nn,disabled:!d||ue||P>=4,"aria-label":"Zoom in",children:t.jsx(Xn,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.toolBtn} ${a.toolbarButton} ${W?a.toolBtnActive:""}`,onClick:()=>Qe(e=>!e),disabled:!d||ue||!Oe,"aria-label":"Pan canvas",title:"Pan canvas",children:t.jsx(Vn,{size:18})})]}),t.jsx("span",{className:a.toolbarSeparator,"aria-hidden":"true"}),t.jsxs("div",{className:a.toolbarActions,children:[t.jsx("div",{className:a.toolbarSelectionActions,children:z?t.jsxs(t.Fragment,{children:[t.jsxs("div",{ref:ka,className:a.toolbarColorPicker,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton} ${a.colorPickerButton}`,onClick:()=>pt(e=>!e),"aria-label":"Marker color","aria-expanded":ht,title:"Marker color",children:t.jsx("span",{className:a.colorPickerSwatch,style:{backgroundColor:qt},"aria-hidden":"true"})}),ht?t.jsx("div",{className:a.colorPickerPopover,role:"menu","aria-label":"Marker color options",children:$o.map(e=>t.jsx("button",{type:"button",className:`${a.colorOption} ${qt===e?a.colorOptionActive:""}`,style:{backgroundColor:e},onClick:()=>bn(e),"aria-label":`Use marker color ${e}`,"aria-pressed":qt===e},e))}):null]}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:ea,"aria-label":"Delete marker",title:"Delete selected marker",children:t.jsx(Ga,{size:18})}),t.jsx("div",{className:a.toolbarGeometryFields,"aria-label":"Marker geometry percent controls",children:zo(z).map(e=>t.jsxs("label",{className:a.toolbarGeometryField,children:[t.jsx("span",{className:`tf-text-meta ${a.toolbarGeometryLabel}`,children:e.label}),t.jsx("input",{className:`tf-field-shell ${a.toolbarGeometryInput}`,type:"number",min:0,max:100,step:.1,value:e.value,onChange:n=>{const o=Do(n.target.value);o!==null&&te(z.id,i=>Oo(i,e.key,o))},"aria-label":`${e.label} percent`})]},e.key))})]}):null}),t.jsxs("div",{className:a.toolbarUtilities,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.payloadBtn} ${a.toolbarButton}`,onClick:In,disabled:!B||yt,"aria-label":yt?"Loading payload preview":"Preview payload",title:yt?"Loading payload preview":"Preview payload",children:t.jsx(ia,{size:18})}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>pa(!0),"aria-label":"How to use marker tools",title:"How to use marker tools",children:t.jsx(la,{size:18})})]})]})]}),t.jsxs("div",{className:a.canvasWorkspace,children:[t.jsx("div",{className:a.canvasToolRail,"aria-label":"Annotation tools",children:Ya.map(e=>{const n=e.icon;return t.jsx("button",{type:"button",className:`tf-button-ghost ${a.toolRailButton} ${T===e.value?a.toolBtnActive:""}`,onClick:()=>xn(e.value),disabled:!d,title:e.label,"aria-label":`${e.label} tool. ${Pt[e.value].short}`,children:t.jsx(n,{size:18})},e.value)})}),t.jsx("div",{className:a.canvasMain,children:t.jsx("div",{ref:Ee,className:`tf-scrollbar ${a.canvasScroller}`,children:d?t.jsxs(t.Fragment,{children:[ue&&!Gt?t.jsx("div",{className:a.canvasStatusOverlay,"aria-live":"polite",children:t.jsxs("div",{className:a.canvasStatusCard,children:[t.jsx(Ka,{size:20,className:za.spinner}),t.jsx("span",{children:"Loading image…"})]})}):null,Gt?t.jsx("div",{className:a.canvasStatusOverlay,"aria-live":"polite",children:t.jsx("div",{className:a.canvasStatusCard,children:t.jsx("span",{children:"Image failed to load."})})}):null,t.jsx("div",{className:a.canvasFrame,children:t.jsxs("div",{className:a.canvasMedia,style:{width:y.width?`${y.width}px`:void 0,height:y.height?`${y.height}px`:void 0},children:[t.jsx("img",{ref:Wt,src:d.path,alt:d.displayName,className:a.canvasImage,onLoad:()=>{const e=Wt.current;tt({width:e?.naturalWidth||0,height:e?.naturalHeight||0}),Je(!1),Ze(!1)},onError:()=>{tt({width:0,height:0}),Je(!1),Ze(!0)}}),!ue&&!Gt&&B?t.jsxs("div",{ref:_a,className:`${a.overlay} ${T==="select"?a.overlaySelect:""} ${W?a.overlayPan:""}`,onPointerDown:Tn,onPointerMove:Rn,onPointerUp:e=>{$n(e),En()},"data-testid":"annotated-attachment-overlay",onPointerLeave:Pn,onPointerCancel:Bn,children:[t.jsx("svg",{className:a.overlaySvg,viewBox:Jt,preserveAspectRatio:"none","aria-hidden":"true",children:j.filter(e=>e.kind==="arrow").map(e=>{const n=Ao(e,y),o=oa(e),i=S===e.id;return t.jsxs(aa.Fragment,{children:[i?t.jsxs(t.Fragment,{children:[t.jsx("line",{x1:n.shaftX1,y1:n.shaftY1,x2:n.shaftX2,y2:n.shaftY2,stroke:"color-mix(in srgb, var(--surface-elevated) 92%, transparent)",strokeWidth:14,strokeLinecap:"round"}),t.jsx("polygon",{points:n.headPoints,fill:o,stroke:"color-mix(in srgb, var(--surface-elevated) 92%, transparent)",strokeWidth:4,strokeLinejoin:"round"})]}):null,t.jsx("line",{x1:n.shaftX1,y1:n.shaftY1,x2:n.shaftX2,y2:n.shaftY2,stroke:o,strokeWidth:i?10:6,strokeLinecap:"round"}),t.jsx("polygon",{points:n.headPoints,fill:o})]},e.id)})}),t.jsx("svg",{className:a.overlayHitLayer,viewBox:Jt,preserveAspectRatio:"none",children:j.filter(e=>e.kind==="arrow").map(e=>t.jsxs(aa.Fragment,{children:[t.jsx("line",{x1:e.x*y.width,y1:e.y*y.height,x2:e.x2*y.width,y2:e.y2*y.height,className:a.arrowHitArea,"data-testid":`annotated-arrow-hit-${e.id}`,"aria-label":`Arrow marker ${Qt.get(e.id)||0}`,onPointerDown:n=>Fa(n,e),onClick:n=>{n.stopPropagation(),F(e.id)}}),S===e.id?t.jsxs(t.Fragment,{children:[t.jsx("circle",{cx:e.x*y.width,cy:e.y*y.height,r:De.hitRadius,className:a.canvasHandleHit,role:"button",tabIndex:0,"aria-label":"Move arrow tail",onPointerDown:n=>Ha(n,e,"tail")}),t.jsx("circle",{cx:e.x*y.width,cy:e.y*y.height,r:De.visibleRadius,className:a.canvasHandleVisible,"aria-hidden":"true"}),t.jsx("circle",{cx:e.x2*y.width,cy:e.y2*y.height,r:De.hitRadius,className:a.canvasHandleHit,role:"button",tabIndex:0,"aria-label":"Move arrow tip",onPointerDown:n=>Ha(n,e,"tip")}),t.jsx("circle",{cx:e.x2*y.width,cy:e.y2*y.height,r:De.visibleRadius,className:a.canvasHandleVisible,"aria-hidden":"true"})]}):null]},`hit-${e.id}`))}),j.filter(e=>e.kind==="arrow").map(e=>t.jsx("div",{className:`${a.annotationNumberBadge} ${a.arrowNumberBadge}`,style:{left:`${(e.x+e.x2)/2*100}%`,top:`${(e.y+e.y2)/2*100}%`,backgroundColor:oa(e)},"aria-hidden":"true",children:Qt.get(e.id)||0},`arrow-number-${e.id}`)),j.filter(e=>e.kind!=="arrow").map(e=>{const n=oa(e),o=Qt.get(e.id)||0,i={onPointerDown:c=>Fa(c,e),onClick:c=>{c.stopPropagation(),F(e.id)}};return e.kind==="pin"?t.jsx("button",{type:"button",...i,className:`${a.pin} ${S===e.id?a.selected:""}`,style:{left:`${e.x*100}%`,top:`${e.y*100}%`,backgroundColor:n},children:o},e.id):e.kind==="text-note"?t.jsx("button",{type:"button",...i,className:`${a.note} ${S===e.id?a.selected:""}`,style:{left:`${e.x*100}%`,top:`${e.y*100}%`,backgroundColor:n},children:o},e.id):t.jsxs("div",{className:`${a.box} ${S===e.id?a.selected:""}`,style:{left:`${e.x*100}%`,top:`${e.y*100}%`,width:`${e.width*100}%`,height:`${e.height*100}%`,borderColor:n},children:[t.jsx("span",{className:`${a.annotationNumberBadge} ${a.boxNumberBadge}`,style:{backgroundColor:n},"aria-hidden":"true",children:o}),t.jsx("button",{type:"button",...i,className:a.boxSurface,"aria-label":`Box marker ${o}`})]},e.id)}),t.jsx("svg",{className:a.overlaySvg,viewBox:Jt,preserveAspectRatio:"none",children:j.filter(e=>e.kind==="box"&&S===e.id).map(e=>t.jsxs(aa.Fragment,{children:[t.jsx("circle",{cx:(e.x+e.width)*y.width,cy:(e.y+e.height)*y.height,r:De.hitRadius,className:`${a.canvasHandleHit} ${a.canvasResizeHandleHit}`,role:"button",tabIndex:0,"aria-label":"Resize box marker",onPointerDown:n=>An(n,e)}),t.jsx("circle",{cx:(e.x+e.width)*y.width,cy:(e.y+e.height)*y.height,r:De.visibleRadius,className:a.canvasHandleVisible,"aria-hidden":"true"})]},`box-handle-${e.id}`))})]}):null]})})]}):t.jsx("div",{className:a.emptyState,children:t.jsx("p",{className:"tf-empty-copy",children:"Select an image attachment to annotate."})})})})]}),t.jsxs("div",{className:a.statusBar,children:[t.jsx("span",{children:K==="error"?"Save failed. Retry now.":K==="saving"||K==="pending"?"Saving…":"Saved"}),ma?t.jsx("span",{children:ma}):t.jsx("span",{children:B?W?"Drag on the image to pan.":T==="select"?"Select a marker to edit it.":`Click on the image to place a ${T}.`:"Create a session to begin placing markers on this image."}),d&&!B&&sn?t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn} ${a.toolbarButton}`,onClick:()=>{Ea()},disabled:V||gt,children:V?"Creating…":"Create one now"}):null]})]}),t.jsx("section",{className:`tf-surface-panel ${a.panel} ${a.detailPanel}`,children:B?t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:a.panelHeader,children:[t.jsxs("div",{className:a.panelHeaderText,children:[t.jsx("div",{className:`tf-heading-card ${a.panelTitle}`,children:"Markers"}),t.jsxs("div",{className:"tf-text-secondary",children:[j.length," in this session"]})]}),t.jsxs("div",{className:a.sessionActions,children:[t.jsx("button",{type:"button",className:`tf-control-icon ${a.iconButton}`,onClick:()=>La("up"),disabled:!z||Vt<=0,"aria-label":"Move marker up",children:t.jsx(Jn,{size:16})}),t.jsx("button",{type:"button",className:`tf-control-icon ${a.iconButton}`,onClick:()=>La("down"),disabled:!z||Vt===-1||Vt>=j.length-1,"aria-label":"Move marker down",children:t.jsx(Zn,{size:16})})]})]}),t.jsx("div",{className:`tf-scrollbar ${a.annotationList}`,children:j.length===0?t.jsx("div",{className:a.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Add a marker on the image to begin."})}):j.map((e,n)=>{const o=Co[e.kind],i=jo[e.kind],c=`${i} ${n+1}`,h=To[e.markerType||ce],w=Wa.find(m=>m.value===(e.markerType||ce))?.label||"Review",u=S===e.id;return t.jsxs("div",{className:`tf-surface-elevated ${a.annotationCard} ${u?a.annotationCardActive:""}`,children:[t.jsx("button",{type:"button",className:a.annotationCardButton,onClick:()=>F(e.id),"aria-label":i,children:t.jsxs("div",{className:a.annotationMeta,children:[t.jsx("span",{className:`tf-heading-card ${a.annotationTitle}`,children:c}),t.jsx("span",{className:`tf-text-meta ${a.annotationKind}`,"aria-hidden":"true",children:t.jsx(o,{size:16})})]})}),u?t.jsxs("div",{className:a.annotationInstructionEditor,children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-marker-instruction",children:"Marker instruction"}),t.jsx("textarea",{id:"annotated-marker-instruction",ref:u?Ia:null,className:`tf-field-shell ${a.textArea} ${a.annotationInstructionField}`,value:e.instruction,onChange:m=>{te(e.id,_=>({..._,instruction:m.target.value}),600)},onBlur:()=>{M("text")},placeholder:"What should the AI focus on for this marker?"}),t.jsxs("div",{className:a.annotationTypeField,children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-marker-type",children:"Instruction type"}),t.jsx("select",{id:"annotated-marker-type",className:`tf-field-shell ${a.select}`,value:e.markerType||ce,onChange:m=>{te(e.id,_=>Mo(_,m.target.value))},children:Wa.map(m=>t.jsx("option",{value:m.value,children:m.label},m.value))})]})]}):t.jsx("button",{type:"button",className:a.annotationInstructionButton,onClick:()=>F(e.id),"aria-label":`Edit ${i} instruction`,children:t.jsxs("div",{className:a.annotationInstructionEditor,children:[t.jsx("div",{className:`tf-text-secondary ${a.annotationInstructionPreview}`,children:e.instruction.trim()||"No marker instruction yet."}),t.jsx("div",{className:a.annotationPreviewFooter,children:t.jsx("span",{className:`tf-text-meta ${a.annotationInstructionTypeIcon}`,"aria-label":`Instruction type: ${w}`,title:w,children:t.jsx(h,{size:16})})})]})})]},e.id)})})]}):t.jsxs("div",{className:a.emptyState,children:[t.jsx("strong",{className:"tf-empty-title",children:"No active session"}),t.jsx("p",{className:"tf-empty-copy",children:"Create or select a session to edit annotations."})]})}),t.jsx(na,{isOpen:ln,onClose:()=>{qe||(vt(!1),_t(""))},title:"Open Image",size:"sm",children:t.jsxs("form",{className:a.openImageModalBody,onSubmit:e=>{e.preventDefault(),gn()},children:[t.jsx("label",{className:"tf-field-label",htmlFor:"annotated-open-image-reference",children:"Image reference number"}),t.jsx("input",{id:"annotated-open-image-reference",className:`tf-field-shell ${a.textInput} ${a.openImageField}`,value:zt,onChange:e=>_t(e.target.value),placeholder:"I-24",autoFocus:!0}),t.jsxs("div",{className:a.openImageActions,children:[t.jsx("button",{type:"button",className:"tf-button-ghost tf-button-compact",onClick:()=>{vt(!1),_t("")},disabled:qe,children:"Cancel"}),t.jsx("button",{type:"submit",className:"tf-button-primary tf-button-compact",disabled:qe,children:qe?"Opening…":"Open"})]})]})}),t.jsx(na,{isOpen:on,onClose:()=>pa(!1),title:"How To Use Markers",size:"md",children:t.jsx("div",{className:a.markerHelpModalBody,children:Ya.filter(e=>e.value!=="select").map(e=>t.jsxs("div",{className:`tf-surface-inset ${a.markerHelpItem}`,children:[t.jsxs("div",{className:a.markerHelpHeader,children:[t.jsx("strong",{className:"tf-heading-card",children:e.label}),t.jsx("span",{className:"tf-text-meta",children:Pt[e.value].short})]}),t.jsx("p",{className:"tf-text-secondary",children:Pt[e.value].detail}),t.jsx("p",{className:`tf-text-body ${a.markerHelpExample}`,children:Pt[e.value].example})]},e.value))})}),t.jsx(na,{isOpen:rn,onClose:()=>Ft(!1),title:"AI Payload",size:"lg",children:t.jsxs("div",{className:a.payloadModalBody,children:[t.jsxs("div",{className:a.payloadModalToolbar,children:[t.jsxs("div",{className:a.payloadViewToggle,children:[t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.toolBtn} ${Ht==="json"?a.toolBtnActive:""}`,onClick:()=>ha("json"),children:"JSON"}),t.jsx("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.toolBtn} ${Ht==="brief"?a.toolBtnActive:""}`,onClick:()=>ha("brief"),children:"AI Brief"})]}),t.jsxs("div",{className:a.payloadModalActions,children:[t.jsxs("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn}`,onClick:()=>{Ma("json")},disabled:!oe,"aria-label":de==="json"?"Copied JSON":"Copy JSON",title:de==="json"?"Copied JSON":"Copy JSON",children:[de==="json"?t.jsx(ca,{size:16}):t.jsx(sa,{size:16}),t.jsx("span",{children:"JSON"})]}),t.jsxs("button",{type:"button",className:`tf-button-ghost tf-button-compact ${a.ghostBtn}`,onClick:()=>{Ma("brief")},disabled:!oe,"aria-label":de==="brief"?"Copied AI Brief":"Copy AI Brief",title:de==="brief"?"Copied AI Brief":"Copy AI Brief",children:[de==="brief"?t.jsx(ca,{size:16}):t.jsx(sa,{size:16}),t.jsx("span",{children:"AI Brief"})]})]})]}),yt?t.jsx("div",{className:a.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Loading payload…"})}):oe?t.jsx("pre",{className:`tf-surface-inset tf-scrollbar ${a.payloadModalPreview}`,children:Ht==="json"?JSON.stringify(oe,null,2):Va(oe)}):t.jsx("div",{className:a.detailEmpty,children:t.jsx("p",{className:"tf-empty-copy",children:"Load a payload preview to inspect the current session contract."})})]})})]})}export{Qo as AnnotatedAttachmentWorkspaceShell};
@@ -1,3 +1,3 @@
1
- import{r as y,j as e}from"./vendor-react-CKJs5o3c.js";import{t as s,Z as Et,_ as St,$ as pt,a0 as dt,a1 as Lt}from"./index-DUt7ifSO.js";import{g as Dt}from"./documentReferences-DXW5aT08.js";import{h as Pt,aM as K,c as X,m as Ut,aN as $t,aI as ut,T as ht,ax as Ft}from"./vendor-icons-CLnehDTw.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";const gt=[".png",".jpg",".jpeg",".webp",".gif",".pdf",".txt",".md",".csv",".json",".doc",".docx",".html",".js",".ts",".tsx",".css",".py",".java",".go",".rs",".sh"].join(","),Rt=new Set(["image/png","image/jpeg","image/webp"]),zt=new Set(gt.split(",").map(o=>o.trim().toLowerCase())),Ot=10*1024*1024,_t=3500;function xt(o){const c=o.trim().toLowerCase(),m=c.lastIndexOf(".");return m===-1?"":c.slice(m)}function J(o){if(o.length===0)return"";const c=o.slice(0,3).join(", "),m=o.length-3;return m>0?`${c}, +${m} more`:c}function Bt(o){return`${o.name.trim().toLowerCase()}::${o.size}::${o.lastModified}`}function Mt(o){return new Promise((c,m)=>{const g=new FileReader;g.onload=()=>c(String(g.result||"")),g.onerror=()=>m(g.error||new Error("Failed to read file")),g.readAsDataURL(o)})}function L(o){return/\.(png|jpe?g|webp|gif)(\?|#|$)/i.test(o)}function Ht(o){const c=o.types;if(!c)return null;for(const m of Array.from(c)){const g=String(m||"").trim().toLowerCase();if(Rt.has(g))return g}return null}function ft(o,c){const m=o.includes("?")?"&":"?";return`${o}${m}download=1&filename=${encodeURIComponent(c)}`}function mt(o){return typeof o=="string"?o.split("/").pop()||"Context file":o.displayName||o.originalFilename||o.caption||o.fsPath||o.path.split("/").pop()||"Context file"}function Kt(o){const c=typeof o=="string"?[o]:[o.originalFilename,o.displayName,o.caption,o.fsPath,o.path];for(const g of c){if(!g)continue;const D=xt(g);if(D)return D.replace(".","").slice(0,5).toUpperCase()}const m=typeof o=="string"?o:o.path;return/^https?:\/\//i.test(m)?"LINK":"FILE"}function Qt(o){const{ownerType:c="task",ownerId:m,ownerReferenceLabel:g,workspaceId:D,attachments:N,onAddAttachment:Y,onRemoveAttachment:G,onUpdateAttachmentCaption:W}=o,v=y.useRef(null),[P,Z]=y.useState(!1),[V,C]=y.useState(null),[$,q]=y.useState(""),[F,A]=y.useState(null),[T,b]=y.useState(null),[R,yt]=y.useState(!1),[wt,z]=y.useState(!1),[Xt,O]=y.useState(0),I=y.useRef(new Set),Q=y.useRef(new Set),tt=t=>(t||"").trim(),U=(t,n)=>{const a=[],r=tt(t),p=tt(n);return r&&a.push(`path:${r}`),p&&a.push(`fs:${p}`),a},et=y.useMemo(()=>{const t=new Set;return N.forEach(n=>{if(typeof n=="string"){U(n).forEach(a=>t.add(a));return}U(n.path,n.fsPath).forEach(a=>t.add(a))}),t},[N]);y.useEffect(()=>{I.current=new Set(et)},[et]);const nt=(t,n)=>n.some(a=>t.has(a)),at=(t,n)=>n.forEach(a=>t.add(a)),E=String(D||"").trim(),j=String(m||"").trim(),st=c==="initiative"?"initiative":c==="workstream"?"workstream":"task",_={ownerType:c,ownerId:j||null,taskId:c==="task"&&j||null},B=(t="application/json")=>{const n={"Content-Type":t};return E&&(n["x-taskforce-workspace-id"]=E),n},ot=(t,n=!0,a=I.current)=>{const r=t.trim();if(!r||!/^https?:\/\//i.test(r))return"invalid";const i=r,f=U(i);if(nt(a,f))return"duplicate";const u=r.split(/[\\/]/).pop()||r;return Y({path:i,caption:u,timestamp:new Date().toISOString()}),at(a,f),n&&q(""),A(null),"added"};y.useEffect(()=>{if(!T)return;const t=window.setTimeout(()=>{b(null)},_t);return()=>window.clearTimeout(t)},[T]);const rt=async t=>{if(t.length===0)return;C(null),b(null);const n=t,a=[],r=[],p=[],i=[],f=new Set(Q.current);n.forEach(x=>{const h=xt(x.name);if(!zt.has(h)){a.push(x.name);return}if(x.size>Ot){r.push(x.name);return}const S=Bt(x);if(f.has(S)){p.push(x.name);return}f.add(S),i.push({file:x,fingerprint:S})});const u=[];if(a.length>0&&u.push(`${a.length} unsupported file${a.length===1?"":"s"} skipped: ${J(a)}.`),r.length>0&&u.push(`${r.length} file${r.length===1?"":"s"} over 10 MB skipped: ${J(r)}.`),p.length>0&&u.push(`${p.length} local duplicate file${p.length===1?"":"s"} skipped before upload: ${J(p)}.`),i.length===0){u.length>0&&b(u.join(" ")),v.current&&(v.current.value="");return}Z(!0);const k=new Set(I.current);let d=0;try{for(const{file:h,fingerprint:S}of i){let l=null;const lt=await fetch("/api/taskforce/context-upload/init",{method:"POST",headers:B(),body:JSON.stringify({originalName:h.name,mimeType:h.type||"application/octet-stream",size:h.size,..._,workspaceId:E||null})});if(lt.ok){const w=await lt.json();if(w?.success&&typeof w?.uploadUrl=="string"&&typeof w?.path=="string"){if(!(await fetch(w.uploadUrl,{method:String(w.method||"PUT"),headers:w.headers||{"Content-Type":h.type||"application/octet-stream"},body:h})).ok)throw new Error(`Upload failed for ${h.name}`);const H=await fetch("/api/taskforce/context-upload/finalize",{method:"POST",headers:B(),body:JSON.stringify({relativePath:w.relativePath,mimeType:h.type||"application/octet-stream",originalName:h.name,size:h.size,..._,workspaceId:E||null})});if(!H.ok){const It=await H.json().catch(()=>({}));throw new Error(It?.error||`Upload failed for ${h.name}`)}l=await H.json().catch(()=>null)}else w?.success&&typeof w?.path=="string"&&(l=w)}if(!l){const w=await Mt(h),M=await fetch("/api/taskforce/context-upload",{method:"POST",headers:B(),body:JSON.stringify({file:w,originalName:h.name,..._,workspaceId:E||null})});if(!M.ok)throw new Error(`Upload failed for ${h.name}`);l=await M.json()}if(!l?.success||!l?.path)throw new Error(`Upload failed for ${h.name}`);const ct=U(l.path,l.fsPath);if(nt(k,ct)){d+=1;continue}Y({path:l.path,fsPath:l.fsPath,caption:typeof l.caption=="string"&&l.caption.trim().length>0?l.caption:h.name,displayName:typeof l.displayName=="string"?l.displayName:void 0,originalFilename:typeof l.originalFilename=="string"?l.originalFilename:h.name,assetId:typeof l.assetId=="string"?l.assetId:void 0,referenceNumber:typeof l.referenceNumber=="number"?l.referenceNumber:null,referenceLabel:typeof l.referenceLabel=="string"?l.referenceLabel:void 0,taskId:c==="task"&&typeof l.taskId=="string"?l.taskId:null,timestamp:typeof l.timestamp=="string"&&l.timestamp.trim().length>0?l.timestamp:new Date().toISOString()}),Lt({workspaceId:E||"default",ownerType:c,ownerId:j||null,taskId:c==="task"&&j||null,reason:"upload"}),at(k,ct),Q.current.add(S)}const x=[...u];d>0&&x.push(`${d} duplicate file${d===1?"":"s"} skipped.`),b(x.length>0?x.join(" "):null),I.current=k}catch(x){C(x instanceof Error?x.message:"Failed to upload context file")}finally{Z(!1),v.current&&(v.current.value="")}},it=async t=>{!t||t.length===0||await rt(Array.from(t))},kt=async()=>{if(C(null),b(null),!navigator.clipboard||typeof navigator.clipboard.read!="function"){C("Clipboard image paste is not available in this browser.");return}try{const t=await navigator.clipboard.read(),n=[];for(const a of t){const r=Ht(a);if(!r)continue;const p=await a.getType(r),i=r.toLowerCase()==="image/png"?"png":r.toLowerCase()==="image/webp"?"webp":"jpg";n.push(new globalThis.File([p],`pasted-image-${Date.now()}.${i}`,{type:r}))}if(n.length===0){C("Clipboard does not currently contain a supported image.");return}await rt(n)}catch(t){C(t?.message||"Failed to read an image from the clipboard.")}},bt=t=>{const n=new Set,a=t.dataTransfer.getData("text/uri-list")||"",r=t.dataTransfer.getData("text/plain")||"",p=`${a}
1
+ import{r as y,j as e}from"./vendor-react-CKJs5o3c.js";import{t as s,Z as Et,_ as St,$ as pt,a0 as dt,a1 as Lt}from"./index-CWg2olz9.js";import{g as Dt}from"./documentReferences-BhNx80zO.js";import{h as Pt,aM as K,c as X,m as Ut,aN as $t,aI as ut,T as ht,ax as Ft}from"./vendor-icons-CLnehDTw.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";const gt=[".png",".jpg",".jpeg",".webp",".gif",".pdf",".txt",".md",".csv",".json",".doc",".docx",".html",".js",".ts",".tsx",".css",".py",".java",".go",".rs",".sh"].join(","),Rt=new Set(["image/png","image/jpeg","image/webp"]),zt=new Set(gt.split(",").map(o=>o.trim().toLowerCase())),Ot=10*1024*1024,_t=3500;function xt(o){const c=o.trim().toLowerCase(),m=c.lastIndexOf(".");return m===-1?"":c.slice(m)}function J(o){if(o.length===0)return"";const c=o.slice(0,3).join(", "),m=o.length-3;return m>0?`${c}, +${m} more`:c}function Bt(o){return`${o.name.trim().toLowerCase()}::${o.size}::${o.lastModified}`}function Mt(o){return new Promise((c,m)=>{const g=new FileReader;g.onload=()=>c(String(g.result||"")),g.onerror=()=>m(g.error||new Error("Failed to read file")),g.readAsDataURL(o)})}function L(o){return/\.(png|jpe?g|webp|gif)(\?|#|$)/i.test(o)}function Ht(o){const c=o.types;if(!c)return null;for(const m of Array.from(c)){const g=String(m||"").trim().toLowerCase();if(Rt.has(g))return g}return null}function ft(o,c){const m=o.includes("?")?"&":"?";return`${o}${m}download=1&filename=${encodeURIComponent(c)}`}function mt(o){return typeof o=="string"?o.split("/").pop()||"Context file":o.displayName||o.originalFilename||o.caption||o.fsPath||o.path.split("/").pop()||"Context file"}function Kt(o){const c=typeof o=="string"?[o]:[o.originalFilename,o.displayName,o.caption,o.fsPath,o.path];for(const g of c){if(!g)continue;const D=xt(g);if(D)return D.replace(".","").slice(0,5).toUpperCase()}const m=typeof o=="string"?o:o.path;return/^https?:\/\//i.test(m)?"LINK":"FILE"}function Qt(o){const{ownerType:c="task",ownerId:m,ownerReferenceLabel:g,workspaceId:D,attachments:N,onAddAttachment:Y,onRemoveAttachment:G,onUpdateAttachmentCaption:W}=o,v=y.useRef(null),[P,Z]=y.useState(!1),[V,C]=y.useState(null),[$,q]=y.useState(""),[F,A]=y.useState(null),[T,b]=y.useState(null),[R,yt]=y.useState(!1),[wt,z]=y.useState(!1),[Xt,O]=y.useState(0),I=y.useRef(new Set),Q=y.useRef(new Set),tt=t=>(t||"").trim(),U=(t,n)=>{const a=[],r=tt(t),p=tt(n);return r&&a.push(`path:${r}`),p&&a.push(`fs:${p}`),a},et=y.useMemo(()=>{const t=new Set;return N.forEach(n=>{if(typeof n=="string"){U(n).forEach(a=>t.add(a));return}U(n.path,n.fsPath).forEach(a=>t.add(a))}),t},[N]);y.useEffect(()=>{I.current=new Set(et)},[et]);const nt=(t,n)=>n.some(a=>t.has(a)),at=(t,n)=>n.forEach(a=>t.add(a)),E=String(D||"").trim(),j=String(m||"").trim(),st=c==="initiative"?"initiative":c==="workstream"?"workstream":"task",_={ownerType:c,ownerId:j||null,taskId:c==="task"&&j||null},B=(t="application/json")=>{const n={"Content-Type":t};return E&&(n["x-taskforce-workspace-id"]=E),n},ot=(t,n=!0,a=I.current)=>{const r=t.trim();if(!r||!/^https?:\/\//i.test(r))return"invalid";const i=r,f=U(i);if(nt(a,f))return"duplicate";const u=r.split(/[\\/]/).pop()||r;return Y({path:i,caption:u,timestamp:new Date().toISOString()}),at(a,f),n&&q(""),A(null),"added"};y.useEffect(()=>{if(!T)return;const t=window.setTimeout(()=>{b(null)},_t);return()=>window.clearTimeout(t)},[T]);const rt=async t=>{if(t.length===0)return;C(null),b(null);const n=t,a=[],r=[],p=[],i=[],f=new Set(Q.current);n.forEach(x=>{const h=xt(x.name);if(!zt.has(h)){a.push(x.name);return}if(x.size>Ot){r.push(x.name);return}const S=Bt(x);if(f.has(S)){p.push(x.name);return}f.add(S),i.push({file:x,fingerprint:S})});const u=[];if(a.length>0&&u.push(`${a.length} unsupported file${a.length===1?"":"s"} skipped: ${J(a)}.`),r.length>0&&u.push(`${r.length} file${r.length===1?"":"s"} over 10 MB skipped: ${J(r)}.`),p.length>0&&u.push(`${p.length} local duplicate file${p.length===1?"":"s"} skipped before upload: ${J(p)}.`),i.length===0){u.length>0&&b(u.join(" ")),v.current&&(v.current.value="");return}Z(!0);const k=new Set(I.current);let d=0;try{for(const{file:h,fingerprint:S}of i){let l=null;const lt=await fetch("/api/taskforce/context-upload/init",{method:"POST",headers:B(),body:JSON.stringify({originalName:h.name,mimeType:h.type||"application/octet-stream",size:h.size,..._,workspaceId:E||null})});if(lt.ok){const w=await lt.json();if(w?.success&&typeof w?.uploadUrl=="string"&&typeof w?.path=="string"){if(!(await fetch(w.uploadUrl,{method:String(w.method||"PUT"),headers:w.headers||{"Content-Type":h.type||"application/octet-stream"},body:h})).ok)throw new Error(`Upload failed for ${h.name}`);const H=await fetch("/api/taskforce/context-upload/finalize",{method:"POST",headers:B(),body:JSON.stringify({relativePath:w.relativePath,mimeType:h.type||"application/octet-stream",originalName:h.name,size:h.size,..._,workspaceId:E||null})});if(!H.ok){const It=await H.json().catch(()=>({}));throw new Error(It?.error||`Upload failed for ${h.name}`)}l=await H.json().catch(()=>null)}else w?.success&&typeof w?.path=="string"&&(l=w)}if(!l){const w=await Mt(h),M=await fetch("/api/taskforce/context-upload",{method:"POST",headers:B(),body:JSON.stringify({file:w,originalName:h.name,..._,workspaceId:E||null})});if(!M.ok)throw new Error(`Upload failed for ${h.name}`);l=await M.json()}if(!l?.success||!l?.path)throw new Error(`Upload failed for ${h.name}`);const ct=U(l.path,l.fsPath);if(nt(k,ct)){d+=1;continue}Y({path:l.path,fsPath:l.fsPath,caption:typeof l.caption=="string"&&l.caption.trim().length>0?l.caption:h.name,displayName:typeof l.displayName=="string"?l.displayName:void 0,originalFilename:typeof l.originalFilename=="string"?l.originalFilename:h.name,assetId:typeof l.assetId=="string"?l.assetId:void 0,referenceNumber:typeof l.referenceNumber=="number"?l.referenceNumber:null,referenceLabel:typeof l.referenceLabel=="string"?l.referenceLabel:void 0,taskId:c==="task"&&typeof l.taskId=="string"?l.taskId:null,timestamp:typeof l.timestamp=="string"&&l.timestamp.trim().length>0?l.timestamp:new Date().toISOString()}),Lt({workspaceId:E||"default",ownerType:c,ownerId:j||null,taskId:c==="task"&&j||null,reason:"upload"}),at(k,ct),Q.current.add(S)}const x=[...u];d>0&&x.push(`${d} duplicate file${d===1?"":"s"} skipped.`),b(x.length>0?x.join(" "):null),I.current=k}catch(x){C(x instanceof Error?x.message:"Failed to upload context file")}finally{Z(!1),v.current&&(v.current.value="")}},it=async t=>{!t||t.length===0||await rt(Array.from(t))},kt=async()=>{if(C(null),b(null),!navigator.clipboard||typeof navigator.clipboard.read!="function"){C("Clipboard image paste is not available in this browser.");return}try{const t=await navigator.clipboard.read(),n=[];for(const a of t){const r=Ht(a);if(!r)continue;const p=await a.getType(r),i=r.toLowerCase()==="image/png"?"png":r.toLowerCase()==="image/webp"?"webp":"jpg";n.push(new globalThis.File([p],`pasted-image-${Date.now()}.${i}`,{type:r}))}if(n.length===0){C("Clipboard does not currently contain a supported image.");return}await rt(n)}catch(t){C(t?.message||"Failed to read an image from the clipboard.")}},bt=t=>{const n=new Set,a=t.dataTransfer.getData("text/uri-list")||"",r=t.dataTransfer.getData("text/plain")||"",p=`${a}
2
2
  ${r}`.split(`
3
3
  `).map(i=>i.trim()).filter(i=>!!i&&!i.startsWith("#"));for(const i of p){if(/^file:\/\//i.test(i)){try{const f=new URL(i),u=decodeURIComponent(f.pathname||"").trim();u&&n.add(u)}catch{}continue}n.add(i)}return Array.from(n)},jt=t=>{if(t.preventDefault(),t.stopPropagation(),z(!1),O(0),C(null),A(null),b(null),t.dataTransfer.files&&t.dataTransfer.files.length>0){it(t.dataTransfer.files);return}const n=bt(t);if(n.length===0){A("Drop a file or URL.");return}const a=new Set(I.current);let r=0,p=0,i=0;n.forEach(u=>{const k=ot(u,!1,a);k==="duplicate"&&(r+=1),k==="added"&&(p+=1),k==="invalid"&&(i+=1)});const f=[];r>0&&f.push(`${r} duplicate link${r===1?"":"s"} skipped.`),i>0&&f.push(`${i} local path${i===1?"":"s"} skipped. Upload files to attach them, or drop an http(s) URL to link.`),b(f.length>0?f.join(" "):p>0?null:T),I.current=a},Nt=t=>{t.preventDefault(),t.stopPropagation(),O(n=>n+1),z(!0)},Ct=t=>{t.preventDefault(),t.stopPropagation(),O(n=>{const a=Math.max(0,n-1);return a===0&&z(!1),a})},vt=t=>{t.preventDefault(),t.stopPropagation(),t.dataTransfer.dropEffect="copy"},At=N.filter(t=>{const n=typeof t=="string"?t:t.path;return L(n)}),Tt=N.filter(t=>{const n=typeof t=="string"?t:t.path;return!L(n)});return e.jsxs("div",{className:s.specialistSection,children:[e.jsx(Et,{label:"Context Documents",count:N.length,expanded:R,expandedTitle:"Hide context documents",collapsedTitle:"Show context documents",onToggle:()=>yt(!R)}),R&&e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:s.settingsHint,children:["Upload files into ",st," context storage, or add an external http(s) URL."]}),e.jsxs("div",{className:s.contextUploadPanel,children:[e.jsxs("div",{className:s.contextUploadActions,children:[e.jsxs("button",{type:"button",className:s.secondaryHeaderBtn,onClick:()=>v.current?.click(),disabled:P,children:[P?e.jsx(Pt,{size:12,className:s.spinner}):e.jsx(K,{size:12}),P?"Uploading...":"Upload File"]}),e.jsxs("button",{type:"button",className:s.secondaryHeaderBtn,onClick:()=>{kt()},disabled:P,children:[e.jsx(X,{size:12}),"Paste Image from Clipboard"]})]}),e.jsx("input",{ref:v,type:"file",accept:gt,multiple:!0,className:s.hiddenInput,onChange:t=>{it(t.target.files)}}),V&&e.jsx("div",{className:s.error,children:V}),e.jsxs("div",{className:s.contextLinkRow,children:[e.jsx("input",{type:"text",className:s.input,placeholder:"Add external URL (https://...)",value:$,onChange:t=>{q(t.target.value),F&&A(null),T&&b(null)}}),e.jsx("button",{type:"button",className:s.secondaryHeaderBtn,onClick:()=>{if(!$.trim()){A("Enter an http(s) URL to link.");return}const t=ot($,!0);if(t==="invalid"){A("Only external http(s) URLs can be linked here. Upload files to attach them.");return}t==="duplicate"?b(`Link already exists in ${st} context.`):t==="added"&&b(null)},children:"Add URL"})]}),e.jsxs("div",{className:`${s.contextDropzone} ${wt?s.contextDropzoneActive:""}`,onDragEnter:Nt,onDragLeave:Ct,onDragOver:vt,onDrop:jt,children:[e.jsx(K,{size:14}),e.jsx("span",{children:"Drop files or URLs here"})]})]}),T&&e.jsx("div",{className:s.contextNotice,children:T}),F&&e.jsx("div",{className:s.error,children:F}),At.length>0&&e.jsx("div",{className:s.attachmentGrid,children:N.map((t,n)=>{const a=typeof t=="string"?t:t.path;if(!L(a))return null;const r=typeof t=="string"?void 0:t.fsPath,p=typeof t=="string"?"":t.caption||"",i=mt(t),f=L(a),u=/^https?:\/\//i.test(a),k=c==="task"&&St(t);return e.jsxs("div",{className:s.attachmentCard,children:[e.jsxs("div",{className:s.attachmentPreviewContainer,children:[f?e.jsx("img",{src:a,alt:p||`Context file ${n}`,onClick:()=>{c==="task"&&pt(t,{taskId:j,taskReferenceLabel:g})||window.open(a,"_blank")},onError:d=>{d.target.style.display="none",d.target.parentElement.classList.add(s.brokenImage)}}):e.jsxs("button",{type:"button",className:s.contextFileLink,onClick:()=>{const d=c==="task"?typeof t=="string"?{path:t,taskId:j}:{...t,taskId:j}:t;dt(d,r)||window.open(a,"_blank")},title:"Open file",children:[e.jsx(K,{size:16}),e.jsx("span",{children:p||r||a.split("/").pop()||"Context file"})]}),e.jsxs("div",{className:s.brokenImagePlaceholder,children:[e.jsx(Ut,{size:16}),e.jsx("span",{children:"Preview unavailable"})]}),e.jsxs("div",{className:s.attachmentActions,children:[k&&e.jsx("button",{type:"button",className:s.attachmentActionBtn,onClick:d=>{d.stopPropagation(),pt(t,{taskId:j,taskReferenceLabel:g})},title:"Annotate",children:e.jsx($t,{size:12})}),r&&e.jsx("button",{type:"button",className:s.attachmentActionBtn,onClick:d=>{d.stopPropagation(),navigator.clipboard.writeText(r)},title:`Copy path: ${r}`,children:e.jsx(X,{size:12})}),!u&&e.jsx("button",{type:"button",className:s.attachmentActionBtn,onClick:d=>{d.stopPropagation(),window.open(ft(a,p||i),"_blank")},title:"Download",children:e.jsx(ut,{size:12})}),e.jsx("button",{type:"button",className:s.attachmentActionBtn,onClick:d=>{d.stopPropagation(),G(n)},title:"Delete",children:e.jsx(ht,{size:12})})]})]}),e.jsx("input",{type:"text",className:s.attachmentCaptionInput,placeholder:"Add a label...",value:p,onChange:d=>W(n,d.target.value)})]},n)})}),Tt.length>0&&e.jsx("div",{className:s.documentAttachmentList,children:N.map((t,n)=>{const a=typeof t=="string"?t:t.path;if(L(a))return null;const r=typeof t=="string"?void 0:t.fsPath,p=typeof t=="string"?"":t.caption||"",i=mt(t),f=Kt(t),u=typeof t=="string"?"":Dt(t),k=/^https?:\/\//i.test(a);return e.jsxs("div",{className:s.documentAttachmentItem,children:[e.jsxs("div",{className:s.documentAttachmentRow,children:[e.jsxs("button",{type:"button",className:s.documentAttachmentLink,onClick:()=>{const d=c==="task"?typeof t=="string"?{path:t,taskId:j}:{...t,taskId:j}:t;dt(d,r)||window.open(a,"_blank")},title:i,children:[e.jsxs("span",{className:s.documentAttachmentMain,children:[e.jsx("span",{className:s.documentAttachmentIcon,"aria-hidden":"true",children:e.jsx(Ft,{size:13})}),e.jsxs("span",{className:s.documentAttachmentText,children:[e.jsx("span",{className:s.documentAttachmentName,children:i}),u&&e.jsx("span",{className:s.taskIdBadge,children:u})]})]}),e.jsx("span",{className:s.documentAttachmentType,children:f})]}),e.jsxs("div",{className:s.documentAttachmentActions,children:[r&&e.jsx("button",{type:"button",className:s.documentAttachmentActionBtn,onClick:()=>{navigator.clipboard.writeText(r)},title:`Copy path: ${r}`,children:e.jsx(X,{size:12})}),!k&&e.jsx("button",{type:"button",className:s.documentAttachmentActionBtn,onClick:()=>{window.open(ft(a,p||i),"_blank")},title:"Download",children:e.jsx(ut,{size:12})}),e.jsx("button",{type:"button",className:s.documentAttachmentActionBtn,onClick:()=>G(n),title:"Delete",children:e.jsx(ht,{size:12})})]})]}),e.jsx("input",{type:"text",className:s.documentAttachmentCaptionInput,placeholder:"Add a label...",value:p,onChange:d=>W(n,d.target.value)})]},n)})})]})]})}export{Qt as ContextAttachmentManager};