-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.js
More file actions
294 lines (261 loc) · 114 KB
/
main.js
File metadata and controls
294 lines (261 loc) · 114 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var Q=Object.defineProperty;var de=Object.getOwnPropertyDescriptor;var he=Object.getOwnPropertyNames;var ue=Object.prototype.hasOwnProperty;var ge=($,n)=>{for(var e in n)Q($,e,{get:n[e],enumerable:!0})},me=($,n,e,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of he(n))!ue.call($,s)&&s!==e&&Q($,s,{get:()=>n[s],enumerable:!(t=de(n,s))||t.enumerable});return $};var pe=$=>me(Q({},"__esModule",{value:!0}),$);var ye={};ge(ye,{default:()=>Y});module.exports=pe(ye);var y=require("obsidian");var ae=require("obsidian"),O=class{constructor(n,e="claude-3-5-haiku-20241022"){this.apiUrl="https://api.anthropic.com/v1/messages";this.apiKey=n,this.model=e}async extractTasks(n,e){if(!this.apiKey)return console.warn("No Claude API key found, using fallback extraction"),this.fallbackExtraction(n,e);try{let t=this.buildPrompt(n,e),s=await this.callClaude(t);return this.parseResponse(s,n)}catch(t){return console.error("Claude task extraction failed, using fallback",t),this.fallbackExtraction(n,e)}}buildPrompt(n,e){let t=typeof n=="string"?n:JSON.stringify(n);return`You are an expert at extracting actionable tasks from meeting transcripts. Analyze the following meeting transcript and extract all tasks, action items, and commitments.
MEETING SUBJECT: ${e}
TRANSCRIPT:
${t.substring(0,15e3)} ${t.length>15e3?"... [truncated]":""}
Extract the following information and return as JSON:
1. **tasks** - Array of task objects with:
- description: Clear, actionable task description
- assignee: Person responsible (use actual names from the meeting, default "Unassigned" if unclear)
- priority: "high", "medium", or "low" based on urgency/importance
- confidence: 0-100 score of how confident you are this is a real task
- dueDate: ISO date string if mentioned (optional)
- category: engineering/product/design/documentation/communication/other
- context: Brief context about why this task exists
- rawText: The original text that led to this task
2. **summary** - 2-3 sentence meeting summary
3. **participants** - Array of participant names (extract all names mentioned)
4. **meetingDate** - ISO date string (use today if not specified)
5. **keyDecisions** - Array of important decisions made
6. **nextSteps** - Array of next step objects with:
- description: Clear description of the next step
- assignee: Person responsible (match with participants when possible, default "Unassigned")
- priority: "high", "medium", or "low" based on importance
Guidelines:
- Focus on explicit commitments ("I will", "I'll", "Let me", "I can", "[Name] will")
- Include tasks with deadlines or time constraints
- Capture follow-ups and action items
- Look for "Next steps", "Action items", "To do", "Follow up" sections
- Check for Google Meet's AI-suggested action items or next steps (often at the end)
- Include any items listed under "Suggested next steps" or "Recommended actions"
- Ignore general discussions or past work
- Be conservative - only extract clear tasks
- Only use names that actually appear in the transcript
- Default assignee should be "Unassigned" for unclear ownership
Return ONLY valid JSON, no other text:`}async callClaude(n){var e,t,s,i,a;try{let o=await(0,ae.requestUrl)({url:this.apiUrl,method:"POST",headers:{"x-api-key":this.apiKey,"anthropic-version":"2023-06-01","content-type":"application/json"},body:JSON.stringify({model:this.model,messages:[{role:"user",content:n}],max_tokens:4e3,temperature:.2,system:"You are a task extraction assistant. Always respond with valid JSON only, no markdown or explanations."})});if((s=(t=(e=o.json)==null?void 0:e.content)==null?void 0:t[0])!=null&&s.text)return o.json.content[0].text;throw new Error("Invalid Claude API response structure")}catch(o){throw((i=o.response)==null?void 0:i.status)===401?console.error("Invalid Claude API key"):((a=o.response)==null?void 0:a.status)===429&&console.error("Claude API rate limit exceeded"),o}}parseResponse(n,e){try{let t=n.match(/\{[\s\S]*\}/);if(!t)throw new Error("No JSON found in response");let s=JSON.parse(t[0]),i=this.normalizeTasks(s.tasks||[]),a=s.participants||[],o=this.normalizeNextSteps(s.nextSteps||[],a),{deduplicatedTasks:l,deduplicatedNextSteps:r}=this.deduplicateTasksAndNextSteps(i,o);return{tasks:l,summary:s.summary||"Meeting transcript processed",participants:a,meetingDate:this.parseDate(s.meetingDate)||new Date,keyDecisions:s.keyDecisions||[],nextSteps:r,confidence:this.calculateOverallConfidence(l)}}catch(t){return console.error("Failed to parse Claude response",t),console.debug("Raw response:",n),this.fallbackExtraction(e,"")}}normalizeTasks(n){return n.map(e=>({description:this.cleanDescription(e.description||""),assignee:e.assignee||"Unassigned",priority:this.normalizePriority(e.priority),confidence:this.normalizeConfidence(e.confidence),dueDate:e.dueDate,category:e.category||"other",context:e.context,rawText:e.rawText})).filter(e=>e.description&&e.description.length>5)}cleanDescription(n){return n.replace(/^[-*•]\s*/,"").replace(/\s+/g," ").trim()}normalizePriority(n){let e=String(n).toLowerCase();return e.includes("high")||e==="3"?"high":e.includes("low")||e==="1"?"low":"medium"}normalizeConfidence(n){let e=Number(n);return isNaN(e)?75:Math.min(100,Math.max(0,e))}parseDate(n){if(!n)return null;let e=new Date(n);return isNaN(e.getTime())?null:e}deduplicateTasks(n){let e=new Set;return n.filter(t=>{let s=`${t.description.toLowerCase()}-${t.assignee.toLowerCase()}`;return e.has(s)?!1:(e.add(s),!0)})}normalizeNextSteps(n,e){return Array.isArray(n)?n.map(t=>{if(typeof t=="string"){let s=this.extractAssigneeFromText(t,e);return{description:this.cleanDescription(t),assignee:s||"Unassigned",priority:"medium"}}else if(typeof t=="object"&&t!==null)return{description:this.cleanDescription(t.description||String(t)),assignee:t.assignee||"Unassigned",priority:this.normalizePriority(t.priority||"medium")};return null}).filter(t=>t!==null&&t.description.length>5):[]}extractAssigneeFromText(n,e){for(let s of e)if(n.toLowerCase().includes(s.toLowerCase()))return s;let t=[/^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\s+(?:will|to|should|needs to)/,/(?:assigned to|owner:|assignee:)\s*([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)/i];for(let s of t){let i=n.match(s);if(i&&i[1])return i[1]}return null}deduplicateTasksAndNextSteps(n,e){let t=new Set(n.map(i=>i.description.toLowerCase().replace(/[^\w\s]/g,""))),s=e.filter(i=>{let a=i.description.toLowerCase().replace(/[^\w\s]/g,"");for(let o of t){let l=a.split(/\s+/),r=o.split(/\s+/);if(l.filter(h=>r.includes(h)).length>l.length*.6&&l.length>2)return!1}return!0});return{deduplicatedTasks:n,deduplicatedNextSteps:s}}calculateOverallConfidence(n){if(n.length===0)return 0;let e=n.reduce((t,s)=>t+s.confidence,0);return Math.round(e/n.length)}fallbackExtraction(n,e){let t=[],s=n.split(`
`),i=[/(?:I will|I'll|I can|Let me|I need to|I should|I have to)\s+(.+)/i,/(?:TODO|Action|Task|Follow.?up):\s*(.+)/i,/(?:Next steps?|Action items?):\s*(.+)/i,/\[ \]\s+(.+)/,/^[-*•]\s*(.+(?:will|need to|should|must).+)/i];for(let r of s)for(let c of i){let h=r.match(c);h&&t.push({description:this.cleanDescription(h[1]),assignee:"Unassigned",priority:"medium",confidence:50,category:"other",rawText:r})}let a=[],o=/(?:with|from|to|cc|attendees?:)\s*([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/gi,l;for(;(l=o.exec(n))!==null;)a.includes(l[1])||a.push(l[1]);return{tasks:this.deduplicateTasks(t),summary:e||"Meeting notes",participants:a,meetingDate:new Date,keyDecisions:[],nextSteps:[],confidence:30}}async extractActionItems(n,e){if(!this.apiKey)return console.warn("No Claude API key found, using fallback extraction"),this.fallbackExtraction(n,e);try{let t=this.buildActionItemPrompt(n,e),s=await this.callClaude(t);return this.parseResponse(s,n)}catch(t){return console.error("Claude action item extraction failed, using fallback",t),this.fallbackExtraction(n,e)}}buildActionItemPrompt(n,e){let t=typeof n=="string"?n:JSON.stringify(n);return`You are an expert at extracting actionable tasks from emails. Analyze the following email and extract all action items, tasks, and commitments.
EMAIL SUBJECT: ${e}
EMAIL CONTENT:
${t.substring(0,15e3)} ${t.length>15e3?"... [truncated]":""}
Extract the following information and return as JSON:
1. **tasks** - Array of task objects with:
- description: Clear, actionable task description
- assignee: Person responsible (extract from email, default "Unassigned" if unclear)
- priority: "high", "medium", or "low" based on urgency/importance
- confidence: 0-100 score of how confident you are this is a real task
- dueDate: ISO date string if mentioned (optional)
- category: engineering/product/design/documentation/communication/other
- context: Brief context about why this task exists
- rawText: The original text that led to this task
2. **summary** - 2-3 sentence summary of the email's main purpose
3. **participants** - Array of people mentioned in the email (sender, recipients, mentioned names)
4. **meetingDate** - ISO date string (use email date or today)
5. **keyDecisions** - Array of important decisions or key points from the email
6. **nextSteps** - Array of next step objects with:
- description: Clear description of the next step
- assignee: Person responsible (default "Unassigned")
- priority: "high", "medium", or "low" based on importance
Guidelines for action items:
- Look for explicit requests ("Can you...", "Please...", "Could you...")
- Identify commitments ("I will...", "I'll...", "Let me...")
- Extract deadlines and time constraints
- Include follow-up items ("Need to...", "Should...", "Must...")
- Capture FYI items that require action
- Pay attention to urgent or important markers
- Extract tasks from forwarded emails or threads
- Identify implicit tasks (things that need to happen based on context)
IMPORTANT:
- For assignee matching, look for names in signatures, "From:" fields, and email addresses
- Match assignees with participants when possible
- If multiple people are mentioned, assign to the most relevant person
- Set confidence lower (40-60) if assignee is unclear
- Return ONLY valid JSON, no markdown formatting
- Priority should reflect urgency indicators like "ASAP", "urgent", "by EOD", etc.
Return your response as a single JSON object.`}};var S=require("obsidian");var ne=require("obsidian"),B=class{constructor(n,e="claude-3-5-haiku-20241022"){this.apiUrl="https://api.anthropic.com/v1/messages";this.apiKey=n,this.model=e}async clusterTasks(n,e,t){if(!this.apiKey)return console.warn("No Claude API key found, clustering unavailable"),{clusters:[],standalone:n,totalTasksAnalyzed:n.length,clustersCreated:0,summary:"Clustering unavailable without Claude API key"};if(n.length===0)return{clusters:[],standalone:[],totalTasksAnalyzed:0,clustersCreated:0,summary:"No tasks to cluster"};try{t&&t("Building clustering prompt...");let s=await this.buildClusteringPrompt(n,e);t&&t(`Analyzing ${n.length} tasks with Claude AI...`);let i=await this.callClaude(s);return t&&t("Parsing clustering results..."),this.parseClusteringResponse(i,n)}catch(s){return console.error("Task clustering failed:",s),{clusters:[],standalone:n,totalTasksAnalyzed:n.length,clustersCreated:0,summary:`Clustering failed: ${s.message}`}}}async buildClusteringPrompt(n,e){return`You are an expert at analyzing and clustering similar tasks. Analyze the following tasks and identify groups of similar or related tasks that could potentially be combined or should be tracked together.
IMPORTANT: Use the "Source" and "Context" information to understand what meeting/email each task came from. Tasks from the same source (meeting/email) are often related even if the task descriptions seem different.
TASKS TO ANALYZE:
${(await Promise.all(n.map(async(i,a)=>{let o="";if(e&&i.file)try{let l=await e.read(i.file),r=l.match(/^---\n[\s\S]*?title:\s*(.+?)\n/m),c=r?r[1]:i.file.basename,d=l.replace(/^---\n[\s\S]*?\n---\n/,"").substring(0,500).replace(/\n+/g," ").trim();o=`
Source: "${c}"
Context: ${d}`}catch(l){console.warn(`Failed to load source for task ${a}:`,l)}return`${a}. "${i.text}" [Assignee: ${i.assignee}] [Priority: ${i.priority}] [Category: ${i.category||"none"}] [Due: ${i.dueDate||"none"}]${o}`}))).join(`
`)}
Your goals:
1. **Identify similar tasks** - Tasks that are duplicates, very similar, or closely related
2. **Identify related tasks** - Tasks that are part of the same project/initiative OR from the same meeting/email
3. **Suggest combinations** - When multiple tasks can be combined into one
4. **Preserve distinct tasks** - Don't force unrelated tasks into clusters
Clustering guidelines:
- **Use source context** - Tasks from the same meeting/email about the same topic should be clustered
- Look for similar descriptions, keywords, or objectives
- Consider task categories and assignees
- Group tasks working toward the same goal or project
- Identify duplicate or near-duplicate tasks
- Keep minimum cluster size of 2 tasks
- Don't cluster tasks just because they're from the same assignee
- Consider priority levels when suggesting combinations
For each cluster, provide:
- **title**: Short, descriptive name for the cluster (max 50 chars)
- **description**: Why these tasks belong together (max 100 chars)
- **taskIndices**: Array of task indices (numbers from the list above)
- **priority**: Highest priority among clustered tasks ('high', 'medium', or 'low')
- **suggestedAssignee**: Best assignee if combining (or null)
- **combinedTask**: If tasks should be combined, suggest the combined task description (or null if they should remain separate)
- **confidence**: 0-100 score of how confident you are in this cluster
Return ONLY valid JSON in this format:
{
"clusters": [
{
"title": "Cluster name",
"description": "Why these tasks are related",
"taskIndices": [0, 3, 5],
"priority": "high",
"suggestedAssignee": "John Doe",
"combinedTask": "Combined task description if applicable",
"confidence": 85
}
],
"standaloneIndices": [1, 2, 4],
"summary": "Brief summary of clustering results"
}
Important:
- Every task index (0 to ${n.length-1}) must appear EXACTLY ONCE in either a cluster or standaloneIndices
- Do not duplicate indices
- Do not skip indices
- Return ONLY the JSON, no other text`}async callClaude(n){var e,t,s,i,a;try{let o=await(0,ne.requestUrl)({url:this.apiUrl,method:"POST",headers:{"x-api-key":this.apiKey,"anthropic-version":"2023-06-01","content-type":"application/json"},body:JSON.stringify({model:this.model,messages:[{role:"user",content:n}],max_tokens:8e3,temperature:.2,system:"You are a task clustering assistant. Always respond with valid JSON only, no markdown or explanations. Ensure all strings are properly escaped and no trailing commas exist in arrays or objects."})});if((s=(t=(e=o.json)==null?void 0:e.content)==null?void 0:t[0])!=null&&s.text)return o.json.content[0].text;throw new Error("Invalid Claude API response structure")}catch(o){throw((i=o.response)==null?void 0:i.status)===401?new Error("Invalid Claude API key"):((a=o.response)==null?void 0:a.status)===429?new Error("Claude API rate limit exceeded"):o}}parseClusteringResponse(n,e){var t;try{let s=n.trim();s=s.replace(/^```json\s*/i,"").replace(/^```\s*/,"").replace(/```\s*$/,"");let i=s.match(/\{[\s\S]*\}/);if(!i)throw new Error("No JSON found in response");s=i[0],s=s.replace(/,(\s*[}\]])/g,"$1"),s=s.replace(/"([^"]*)"([^"]*?)"/g,(o,l,r)=>r.includes('"')?`"${l}${r.replace(/"/g,'\\"')}"`:o),console.debug("Attempting to parse JSON, length:",s.length);let a;try{a=JSON.parse(s)}catch(o){let l=(t=o.message.match(/position (\d+)/))==null?void 0:t[1];if(l){let r=parseInt(l),c=Math.max(0,r-100),h=Math.min(s.length,r+100);console.error("JSON parse error near:",s.substring(c,h)),console.error("Error position:",r,"Character:",s[r]),console.error("Last 200 chars:",s.substring(s.length-200));let d=(s.match(/\{/g)||[]).length,m=(s.match(/\}/g)||[]).length,u=(s.match(/\[/g)||[]).length,g=(s.match(/\]/g)||[]).length;if(console.error("Brace balance: { =",d,"} =",m),console.error("Bracket balance: [ =",u,"] =",g),d>m||u>g){console.log("\u26A0\uFE0F JSON appears truncated, attempting to close structures");let f=s,k=d-m,p=u-g;for(;k>0||p>0;)p>0&&(f+="]",p--),k>0&&(f+="}",k--);console.log("Attempting to parse with closures added");try{return a=JSON.parse(f),console.log("\u2713 Successfully parsed with auto-fix!"),this.buildClusteringResult(a,e)}catch(w){console.error("Auto-fix failed:",w.message)}}}throw o}return this.buildClusteringResult(a,e)}catch(s){throw console.error("Failed to parse clustering response:",s),console.debug("Raw response:",n),s}}buildClusteringResult(n,e){let t=[],s=new Set;if(n.clusters&&Array.isArray(n.clusters))for(let a of n.clusters){if(!a.taskIndices||a.taskIndices.length<2)continue;let o=[];for(let l of a.taskIndices)l>=0&&l<e.length&&!s.has(l)&&(o.push(e[l]),s.add(l));o.length>=2&&t.push({id:`cluster-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,title:a.title||"Related Tasks",description:a.description||"Similar tasks",tasks:o,priority:a.priority||"medium",suggestedAssignee:a.suggestedAssignee||void 0,combinedTask:a.combinedTask||void 0,confidence:a.confidence||75})}let i=[];if(n.standaloneIndices&&Array.isArray(n.standaloneIndices))for(let a of n.standaloneIndices)a>=0&&a<e.length&&!s.has(a)&&(i.push(e[a]),s.add(a));for(let a=0;a<e.length;a++)s.has(a)||i.push(e[a]);return{clusters:t,standalone:i,totalTasksAnalyzed:e.length,clustersCreated:t.length,summary:n.summary||`Found ${t.length} clusters from ${e.length} tasks`}}};var H="task-dashboard-view",ee=class extends S.Modal{constructor(n,e,t){super(n),this.currentTitle=e,this.onSubmit=t}onOpen(){let{contentEl:n,titleEl:e}=this;e.setText("Edit Cluster Title"),n.empty(),n.createEl("p",{text:"Enter a new title for this cluster:"});let t=n.createEl("input",{type:"text",value:this.currentTitle});t.style.width="100%",t.style.padding="8px",t.style.marginBottom="16px";let s=n.createDiv();s.style.display="flex",s.style.gap="8px",s.style.justifyContent="flex-end";let i=s.createEl("button",{text:"Save",cls:"mod-cta"}),a=s.createEl("button",{text:"Cancel"});i.onclick=()=>{let o=t.value.trim();o&&this.onSubmit(o),this.close()},a.onclick=()=>{this.close()},t.addEventListener("keydown",o=>{o.key==="Enter"?i.click():o.key==="Escape"&&a.click()}),setTimeout(()=>{t.focus(),t.select()},50)}onClose(){let{contentEl:n}=this;n.empty()}},V=class extends S.ItemView{constructor(e,t){super(e);this.allTasks=[];this.isLoading=!1;this.filterCounts=null;this.badgeElements=new Map;this.updateCountsDebounceTimer=null;this.cachedParticipants=null;this.taskClusterer=null;this.clusteringResult=null;this.showClustered=!1;this.clusterToggleBtn=null;this.customClusterTitles=new Map;this.currentFilter="all";this.activeFilters=new Set;this.component=new S.Component,this.plugin=t}getViewType(){return H}getDisplayText(){return"Task Dashboard"}getIcon(){return"check-square"}async onOpen(){console.log("[TaskDashboard] onOpen called"),await this.refresh()}async onClose(){this.updateCountsDebounceTimer&&(clearTimeout(this.updateCountsDebounceTimer),this.updateCountsDebounceTimer=null),this.component.unload()}async refresh(){console.log("[TaskDashboard] refresh called"),console.log("[TaskDashboard] containerEl:",this.containerEl),console.log("[TaskDashboard] containerEl.children:",this.containerEl.children);let e=this.containerEl.children[1];console.log("[TaskDashboard] container:",e),e.empty(),e.addClass("dashboard"),e.addClass("markdown-preview-view"),this.cachedParticipants=null,this.showLoadingState(e);try{await this.loadAndDisplayDashboard(e)}catch(t){console.error("[TaskDashboard] Failed to refresh dashboard:",t),this.showErrorState(e,t)}}showLoadingState(e){let t=e.createDiv("dashboard-loading");t.createEl("div",{cls:"loading-spinner"}),t.createEl("p",{text:"Loading tasks...",cls:"loading-text"})}showErrorState(e,t){e.empty();let s=e.createDiv("dashboard-error");s.createEl("h2",{text:"\u26A0\uFE0F Error Loading Dashboard"}),s.createEl("p",{text:"Failed to load tasks. Please try refreshing."}),s.createEl("pre",{text:(t==null?void 0:t.message)||"Unknown error",cls:"error-details"});let i=s.createEl("button",{text:"\u{1F504} Retry",cls:"dashboard-control-btn"});i.onclick=()=>this.refresh()}async loadAndDisplayDashboard(e){var f;e.empty();let t=e.createDiv("dashboard-header");t.createEl("h1",{text:"TASK DASHBOARD",cls:"title"});let s=t.createDiv("dashboard-controls");this.clusterToggleBtn=s.createEl("button",{text:this.showClustered?"\u{1F4CB} Show Task List":"\u{1F9E9} Show Clustered View",cls:"dashboard-control-btn dashboard-cluster-btn"}),this.clusterToggleBtn.onclick=async()=>{let k=this.clusterToggleBtn;if(console.log("[ClusterBtn] Clicked. Current showClustered:",this.showClustered),e.querySelectorAll(".task-section").forEach(w=>w.remove()),!this.showClustered)this.showClustered=!0,k.textContent="\u{1F4CB} Show Task List",console.log("[ClusterBtn] Switching to clustered view"),await this.displayClusteredTasks(e),this.activeFilters.size>0&&this.applyMultipleFiltersToClusters();else{this.showClustered=!1,k.textContent="\u{1F9E9} Show Clustered View",console.log("[ClusterBtn] Switching to task list view");let w=this.currentFilter==="delegated"?this.allTasks:this.getFilteredTasks();await this.displayTasks(e,w),this.activeFilters.size>0&&this.applyMultipleFilters()}};let i=s.createEl("button",{text:"\u{1F504} Refresh",cls:"dashboard-control-btn dashboard-refresh-btn"});i.onclick=()=>this.refresh();let a=s.createDiv("recluster-btn-container"),o=a.createEl("button",{text:"\u{1F9E9} Re-cluster Tasks",cls:"dashboard-control-btn recluster-main-btn",title:"Cluster tasks"}),l=a.createEl("button",{text:"\u25BC",cls:"dashboard-control-btn recluster-dropdown-btn",title:"Choose clustering mode"}),r=a.createDiv("recluster-dropdown-menu");r.style.display="none";let c=r.createDiv("dropdown-option");c.createEl("div",{text:"\u{1F9E9} Smart Re-cluster",cls:"option-title"}),c.createEl("div",{text:"Cluster only new tasks (preserves existing clusters)",cls:"option-description"});let h=r.createDiv("dropdown-option");h.createEl("div",{text:"\u{1F527} Force Re-cluster",cls:"option-title"}),h.createEl("div",{text:"Re-analyze ALL tasks (creates fresh clusters)",cls:"option-description"});let d=async k=>{if(await this.clusterCurrentTasks(k),console.log("[Re-cluster] Reloading tasks from disk..."),this.allTasks=await this.loadTasks(),this.clusteringResult=null,this.clusteringResult=this.buildClusteringFromSavedIds(),console.log("[Re-cluster] Clustering result:",this.clusteringResult),this.showClustered)e.querySelectorAll(".task-section").forEach(w=>w.remove()),await this.displayClusteredTasks(e),this.activeFilters.size>0&&this.applyMultipleFiltersToClusters();else if(this.clusteringResult&&this.clusteringResult.clusters.length>0){let p=k?`\u2705 Created ${this.clusteringResult.clusters.length} fresh clusters.`:"\u2705 Clustered new tasks.";new S.Notice(`${p} Click "Show Clustered View" to see them.`)}};o.onclick=async()=>{r.style.display="none",await d(!1)},l.onclick=k=>{k.stopPropagation();let p=r.style.display==="block";r.style.display=p?"none":"block"},c.onclick=async()=>{r.style.display="none",await d(!1)},h.onclick=async()=>{r.style.display="none",await d(!0)},document.addEventListener("click",k=>{a.contains(k.target)||(r.style.display="none")});let m=e.createDiv("dashboard-filters");this.createFilterButtons(m);try{this.isLoading=!0,this.allTasks=await this.loadTasks(),await this.loadCustomClusterTitles()}catch(k){console.error("Failed to load tasks:",k),new S.Notice("Failed to load tasks. Check console for details."),this.allTasks=[]}finally{this.isLoading=!1}this.allTasks.some(k=>k.clusterId)&&!this.clusteringResult&&(this.clusteringResult=this.buildClusteringFromSavedIds(),console.log("[Dashboard] Loaded",((f=this.clusteringResult)==null?void 0:f.clusters.length)||0,"clusters from saved IDs")),this.showClustered&&this.clusterToggleBtn&&(this.clusterToggleBtn.textContent="\u{1F4CB} Show Task List"),console.log("[Dashboard] Current state: showClustered =",this.showClustered),this.updateFilterCounts(!0);let g=this.currentFilter==="delegated"?this.allTasks:this.getFilteredTasks();console.log("[Dashboard] Render state:",{showClustered:this.showClustered,hasClusteringResult:!!this.clusteringResult,currentFilter:this.currentFilter,allTasksCount:this.allTasks.length,displayTasksCount:g.length}),this.showClustered&&this.clusteringResult?(await this.displayClusteredTasks(e),this.activeFilters.size>0&&this.applyMultipleFiltersToClusters()):(console.log("[Dashboard] Calling displayTasks with",g.length,"tasks"),await this.displayTasks(e,g),this.activeFilters.size>0&&this.applyMultipleFilters()),this.applyDashboardStyles()}createBadgeElement(e,t){if(e===0)return null;let s=document.createElement("span");return s.className="filter-badge",s.setAttribute("data-filter-type",t),s.textContent=e.toString(),s}createFilterButtons(e){let t=e.createDiv("filter-group");this.badgeElements.clear();let s=this.getCurrentFilterCounts();this.filterCounts=s,[{label:"\u{1F534} High",filter:"high",active:!1,dataAttr:"high",count:s.high},{label:"\u{1F7E1} Medium",filter:"medium",active:!1,dataAttr:"medium",count:s.medium},{label:"\u23F0 Past Due",filter:"overdue",dataAttr:"overdue",count:s.overdue},{label:"\u{1F4C5} This Week",filter:"week",dataAttr:"due-week",count:s.week},{label:"\u{1F465} Delegated",filter:"delegated",dataAttr:"delegated",count:s.delegated},{label:"\u2705 Done",filter:"completed",dataAttr:"completed",count:s.completed}].forEach(a=>{let o=t.createEl("button",{cls:a.active?"filter-btn active":"filter-btn"});o.setAttribute("data-filter",a.dataAttr);let l=o.createEl("span",{text:a.label,cls:"filter-btn-label"}),r=this.createBadgeElement(a.count,a.dataAttr);r&&(o.appendChild(r),this.badgeElements.set(a.filter,r)),o.onclick=()=>{o.hasClass("active")?(o.removeClass("active"),this.activeFilters.delete(a.filter)):(o.addClass("active"),this.activeFilters.add(a.filter)),console.log("[Filter] Active filters:",Array.from(this.activeFilters)),this.currentFilter=this.activeFilters.size===0?"all":Array.from(this.activeFilters)[0],this.activeFilters.has("delegated")&&this.activeFilters.size===1?this.updateTaskDisplay():this.showClustered?this.applyMultipleFiltersToClusters():this.applyMultipleFilters()}})}async loadTasks(){console.log("[TaskDashboard] loadTasks called");let e=[],t=this.app.vault.getMarkdownFiles();console.log("[TaskDashboard] Found",t.length,"markdown files");for(let s of t){let i=await this.extractTasksFromFile(s);e.push(...i)}return console.log("[TaskDashboard] Loaded",e.length,"total tasks"),e}async extractTasksFromFile(e){let t=[];try{let i=(await this.app.vault.read(e)).split(`
`);for(let a=0;a<i.length;a++){let o=i[a],l=o.match(/^[\s-]*\[([ x])\]\s+(.+)/);if(l){let r=l[1]==="x",c=l[2],h="medium";o.includes("\u23EB")||o.includes("\u{1F53C}")||o.includes("\u{1F534}")||c.includes("High Priority")?h="high":o.includes("\u23EC")||o.includes("\u{1F53D}")||o.includes("\u{1F7E2}")||c.includes("Low Priority")?h="low":o.includes("\u{1F7E1}")&&(h="medium");let d=c.match(/\[\[@?([^\]]+)\]\]/),m=d?d[1]:"Unassigned",u=c.match(/📅\s*(\d{4}-\d{2}-\d{2})/),g=u?u[1]:"",f=c.match(/⚠️\s*(\d+)%/),k=f?parseInt(f[1]):100,p=c.match(/#(\w+)/),w=p?p[1]:"general",C=c.match(/🔗\s*delegated-from:@?([^📆🔗]+?)(?:\s*📆|\s*🔗|\s*$)/),x=C?C[1].trim():void 0,A=c.match(/📆\s*(\d{4}-\d{2}-\d{2})/),v=A?A[1]:void 0,b=[],T=c.matchAll(/🔗@?([^\s]+)📆(\d{4}-\d{2}-\d{2})/g);for(let M of T)b.push({assignee:M[1],date:M[2]});let E=c.match(/🧩\s*cluster:([a-z0-9-]+)/),D=E?E[1]:void 0,L=c.replace(/\[\[@?[^\]]+\]\]/g,"").replace(/📅\s*\d{4}-\d{2}-\d{2}/g,"").replace(/📆\s*\d{4}-\d{2}-\d{2}/g,"").replace(/🔗\s*delegated-from:@?[^📆🔗]+/g,"").replace(/🔗@?[^\s]+📆\d{4}-\d{2}-\d{2}/g,"").replace(/🧩\s*cluster:[a-z0-9-]+/g,"").replace(/[🔴🟡🟢]/g,"").replace(/⚠️\s*\d+%/g,"").replace(/#\w+/g,"").trim();t.push({text:L,completed:r,assignee:m,dueDate:g,priority:h,confidence:k,category:w,file:e,line:a,rawLine:o,delegatedFrom:x,delegatedDate:v,delegationChain:b.length>0?b:void 0,clusterId:D})}}}catch(s){console.error(`Failed to read file ${e.path}:`,s)}return t}async displayTasks(e,t){console.log("[displayTasks] Called with",t.length,"tasks");let s=e.querySelectorAll(".task-section");console.log("[displayTasks] Clearing",s.length,"existing sections"),s.forEach(r=>r.remove());let i=t.filter(r=>r.priority==="high"&&!r.completed),a=t.filter(r=>r.priority==="medium"&&!r.completed),o=t.filter(r=>r.priority==="low"&&!r.completed),l=t.filter(r=>r.completed);if(console.log("[displayTasks] Grouped:",{high:i.length,medium:a.length,low:o.length,completed:l.length}),i.length>0&&(console.log("[displayTasks] Creating high priority section with",i.length,"tasks"),await this.createTaskSection(e,"\u{1F534} High Priority",i,"high")),a.length>0&&(console.log("[displayTasks] Creating medium priority section with",a.length,"tasks"),await this.createTaskSection(e,"\u{1F7E1} Medium Priority",a,"medium")),o.length>0&&(console.log("[displayTasks] Creating low priority section with",o.length,"tasks"),await this.createTaskSection(e,"\u{1F7E2} Low Priority",o,"low")),console.log("[displayTasks] Finished creating sections"),l.length>0){let r=e.createDiv("task-section completed-section"),c=r.createEl("h2",{text:`\u2705 Completed (${l.length})`,cls:"collapsible"}),h=r.createDiv("task-grid collapsed");c.onclick=()=>{h.hasClass("collapsed")?(h.removeClass("collapsed"),c.removeClass("collapsed")):(h.addClass("collapsed"),c.addClass("collapsed"))},await this.createTaskCards(h,l,"completed")}}async createTaskSection(e,t,s,i){let a=e.createDiv(`task-section ${i}-section`);a.createEl("h2",{text:`${t} (${s.length})`});let o=a.createDiv("task-grid");await this.createTaskCards(o,s,i)}async createTaskCards(e,t,s){let i={};t.forEach(o=>{let l=o.assignee||"Unassigned";i[l]||(i[l]=[]),i[l].push(o)});let a=Object.keys(i).sort((o,l)=>{var c,h,d;let r=(d=(h=(c=this.plugin)==null?void 0:c.settings)==null?void 0:h.dashboardMyName)==null?void 0:d.toLowerCase();if(r){let m=r.split(",").map(p=>p.trim()).filter(p=>p.length>0),u=o.toLowerCase(),g=l.toLowerCase(),f=m.some(p=>u.includes(p)),k=m.some(p=>g.includes(p));if(f&&!k)return-1;if(k&&!f)return 1}return o.localeCompare(l)});for(let o of a){let l=e.createDiv(`task-card ${s}-card`),c=l.createDiv("card-header").createEl("h3",{text:`\u{1F464} ${o}`,cls:"card-assignee-title"}),h=l.createEl("ul",{cls:"task-list"});for(let d of i[o]){let m=h.createEl("li",{cls:"task-list-item"}),u=m.createEl("input",{type:"checkbox",cls:"task-checkbox"});u.checked=d.completed,u.onclick=async()=>{await this.toggleTask(d,u.checked,m)};let g=m.createDiv("task-content"),f=g.createEl("span",{text:d.text,cls:d.completed?"task-text completed clickable":"task-text clickable"});f.onclick=async v=>{v.stopPropagation();let b=this.app.workspace.getLeaf(!1);await b.openFile(d.file);let T=b.view;if(T&&"editor"in T){let E=T.editor;E&&(E.setCursor(d.line,0),E.scrollIntoView({from:{line:Math.max(0,d.line-5),ch:0},to:{line:Math.min(E.lineCount()-1,d.line+5),ch:0}}))}},f.title=`Click to open: ${d.file.basename}`;let k=g.createDiv("task-meta"),p=k.createEl("span",{cls:"task-source clickable",text:`\u{1F4C4} ${d.file.basename}`});if(p.onclick=f.onclick,p.title=`Click to open: ${d.file.basename}`,d.dueDate){let v=k.createEl("span",{cls:"task-due"});v.setText(`\u{1F4C5} ${d.dueDate}`),!d.completed&&new Date(d.dueDate)<new Date&&v.addClass("overdue")}if(d.category&&k.createEl("span",{text:`#${d.category}`,cls:"task-category"}),d.confidence&&d.confidence<70&&k.createEl("span",{text:`\u26A0\uFE0F ${d.confidence}%`,cls:"task-confidence"}),d.delegatedFrom){let v=k.createEl("span",{cls:"task-tag task-delegated-tag",text:"DELEGATED",title:`Delegated from ${d.delegatedFrom}${d.delegatedDate?" on "+d.delegatedDate:""}`}),b=k.createEl("span",{cls:"task-delegation",title:`Delegated from ${d.delegatedFrom}${d.delegatedDate?" on "+d.delegatedDate:""}`});b.setText(`\u{1F517} from @${d.delegatedFrom}`),d.delegatedDate&&b.setText(`\u{1F517} from @${d.delegatedFrom} \u{1F4C6} ${d.delegatedDate}`)}if(d.delegationChain&&d.delegationChain.length>0){let v=k.createEl("span",{cls:"task-delegation-chain",title:"Delegation history"}),b=d.delegationChain.map(T=>`@${T.assignee} (${T.date})`).join(" \u2192 ");v.setText(`\u{1F517} Chain: ${b}`)}let w=k.createEl("a",{text:"\u{1F4C4}",cls:"task-source",title:d.file.basename});w.onclick=v=>{v.preventDefault(),this.app.workspace.getLeaf().openFile(d.file)};let C=m.createEl("button",{cls:"task-edit-btn",text:"\u270F\uFE0F",title:"Edit task"}),x=m.createEl("div",{cls:"task-edit-controls"});x.style.display="none";let A=!1;if(C.onclick=()=>{A=!A,x.style.display=A?"block":"none",C.classList.toggle("active",A)},x){let v=x.createDiv("task-edit-row"),b=v.createEl("select",{cls:"task-priority-select"});["high","medium","low"].forEach(P=>{let I=b.createEl("option",{text:P,value:P});P===d.priority&&(I.selected=!0)});let T=v.createEl("input",{type:"text",cls:"task-assignee-input",placeholder:"Type @ to see participants...",value:d.assignee}),E=v.createEl("div",{cls:"task-assignee-autocomplete"});E.style.display="none";let D=[];this.loadAllParticipants().then(P=>{D=P}),T.addEventListener("input",P=>{let I=T.value,R=T.selectionStart||0,X=I.substring(0,R).match(/@(\w*)$/);if(X&&D.length>0){let oe=X[1].toLowerCase(),ie=D.filter(q=>q.toLowerCase().includes(oe));ie.length>0?(E.empty(),ie.forEach(q=>{let re=E.createEl("div",{cls:"autocomplete-item",text:q});re.onclick=()=>{let le=I.substring(0,R-X[0].length),ce=I.substring(R);T.value=le+q+ce,E.style.display="none",T.focus()}}),E.style.display="block"):E.style.display="none"}else E.style.display="none"}),T.addEventListener("blur",()=>{setTimeout(()=>{E.style.display="none"},200)});let L=v.createEl("button",{text:"\u2713",cls:"task-save-btn",title:"Save changes (assignee changes are tracked as delegations)"});L.onclick=async()=>{let P=b.value,I=P!==d.priority,R=T.value.trim(),Z=R!==d.assignee&&R!=="";I&&await this.updateTaskPriority(d,P,m),Z&&await this.updateTaskDelegation(d,R,m),!I&&!Z&&(x.style.display="none",C.classList.remove("active"))};let M=v.createEl("button",{text:"\u2715",cls:"task-cancel-btn",title:"Cancel changes"});M.onclick=()=>{b.value=d.priority,T.value=d.assignee,x.style.display="none",C.classList.remove("active")}}}}}async toggleTask(e,t,s){try{let a=(await this.app.vault.read(e.file)).split(`
`);t?a[e.line]=e.rawLine.replace("[ ]","[x]"):a[e.line]=e.rawLine.replace("[x]","[ ]"),await this.app.vault.modify(e.file,a.join(`
`)),e.completed=t,s&&t?(s.style.transition="opacity 0.3s ease-out, transform 0.3s ease-out",s.style.opacity="0",s.style.transform="translateX(-10px)",setTimeout(()=>{s.remove();let o=s.closest(".task-card");o&&o.querySelectorAll(".task-list-item").length===0&&(o.style.transition="opacity 0.3s ease-out",o.style.opacity="0",setTimeout(()=>{o.remove();let r=o.closest(".task-section");r&&r.querySelectorAll(".task-card").length===0&&(r.style.display="none")},300)),this.updateStatsOnly(),this.updateFilterCounts()},300)):t||setTimeout(()=>this.refresh(),500)}catch(i){console.error("Failed to toggle task:",i),new S.Notice("Failed to update task. Please try again.")}}async updateTaskPriority(e,t,s){try{let a=(await this.app.vault.read(e.file)).split(`
`),o=a[e.line];o=o.replace(/🔴\s*/g,"").replace(/🟡\s*/g,"").replace(/🟢\s*/g,""),o=o.replace(/High Priority/gi,"").replace(/Medium Priority/gi,"").replace(/Low Priority/gi,"");let l=o.match(/^([\s-]*)\[([x ]?)\]\s*/);if(l){let r=l[0],c=o.substring(r.length),h="";t==="high"?h="\u{1F534} ":t==="medium"?h="\u{1F7E1} ":t==="low"&&(h="\u{1F7E2} "),a[e.line]=r+h+c.trim()}if(await this.app.vault.modify(e.file,a.join(`
`)),e.priority=t,s){let r=s.closest(".task-card"),c=r==null?void 0:r.closest(".task-section");if(r&&c){let h=this.containerEl.children[1],d="";t==="high"?d="high-priority":t==="medium"?d="medium-priority":d="low-priority";let m=h.querySelector(`.task-section.${d}`);m&&m!==c&&(s.style.transition="opacity 0.3s ease-out",s.style.opacity="0",setTimeout(()=>{s.remove(),r.querySelectorAll(".task-list-item").length===0&&(r.style.transition="opacity 0.3s ease-out",r.style.opacity="0",setTimeout(()=>r.remove(),300));let g=e.assignee,f=Array.from(m.querySelectorAll(".task-card")).find(p=>{var C;let w=(C=p.querySelector("h3"))==null?void 0:C.textContent;return w==null?void 0:w.includes(g)});f||(f=m.createDiv(`task-card ${t}-card`),f.createDiv("card-header").createEl("h3",{text:`\u{1F464} ${g}`,cls:"card-assignee-title"}),f.createEl("ul",{cls:"task-list"}));let k=f.querySelector(".task-list");if(k){let p=k.createEl("li",{cls:"task-list-item"});p.innerHTML=s.innerHTML;let w=p.querySelector(".task-checkbox");w&&(w.onclick=async()=>{await this.toggleTask(e,w.checked,p)}),p.style.opacity="0",setTimeout(()=>{p.style.transition="opacity 0.3s ease-in",p.style.opacity="1"},10)}this.updateStatsOnly()},300))}}else setTimeout(()=>this.refresh(),500)}catch(i){console.error("Failed to update task priority:",i),new S.Notice("Failed to update priority. Please try again.")}}async updateTaskDelegation(e,t,s){var i,a,o;try{console.log("[TaskDashboard] Delegating task:",e.text,"from",e.assignee,"to",t);let r=(await this.app.vault.read(e.file)).split(`
`),c=r[e.line];console.log("[TaskDashboard] Original line:",c);let h=e.assignee,d=(o=(a=(i=this.plugin)==null?void 0:i.settings)==null?void 0:a.dashboardMyName)==null?void 0:o.toLowerCase(),m=d?d.split(",").map(f=>f.trim()).filter(f=>f.length>0):[],u=t.toLowerCase().trim(),g=m.length>0&&m.some(f=>u===f||u.includes(f)||f.includes(u));if(c=c.replace(/🔗\s*delegated-from:@?[^📆\[]+/g,""),c=c.replace(/📆\s*\d{4}-\d{2}-\d{2}/g,""),c=c.replace(/\[\[@?[^\]]+\]\]/g,""),c=c.replace(/\s+/g," ").trim(),g){let f=c.match(/📅\s*\d{4}-\d{2}-\d{2}/);f&&f.index!==void 0?c=c.substring(0,f.index)+`[[@${t.trim()}]] `+c.substring(f.index):c=c.trim()+` [[@${t.trim()}]]`,console.log("[TaskDashboard] Task assigned back to self, removing delegation metadata"),new S.Notice(`Task assigned back to ${t}`),e.delegatedFrom=void 0,e.delegatedDate=void 0,e.assignee=t.trim()}else{let f=new Date().toISOString().split("T")[0],k=` \u{1F517} delegated-from:@${h} \u{1F4C6} ${f}`,p=c.match(/📅\s*\d{4}-\d{2}-\d{2}/);p&&p.index!==void 0?c=c.substring(0,p.index)+`[[@${t.trim()}]]${k} `+c.substring(p.index):c=c.trim()+` [[@${t.trim()}]]${k}`,console.log("[TaskDashboard] Task delegated successfully. delegatedFrom:",h),new S.Notice(`Task delegated from ${h} to ${t}`),e.delegatedFrom=h,e.delegatedDate=f,e.assignee=t.trim()}console.log("[TaskDashboard] Updated line:",c),r[e.line]=c,await this.app.vault.modify(e.file,r.join(`
`)),setTimeout(()=>this.refresh(),500)}catch(l){console.error("[TaskDashboard] Failed to delegate task:",l),new S.Notice("Failed to delegate task. Please try again.")}}async updateTaskAssignee(e,t,s){try{let a=(await this.app.vault.read(e.file)).split(`
`),o=a[e.line];o=o.replace(/\[\[@?[^\]]+\]\]/g,"");let l=o.match(/📅\s*\d{4}-\d{2}-\d{2}/);l&&l.index!==void 0?o=o.substring(0,l.index)+`[[@${t.trim()}]] `+o.substring(l.index):o=o.trim()+` [[@${t.trim()}]]`,a[e.line]=o,await this.app.vault.modify(e.file,a.join(`
`));let r=e.assignee;if(e.assignee=t.trim(),s&&r!==e.assignee){let c=s.closest(".task-card"),h=c==null?void 0:c.closest(".task-section");c&&h&&(s.style.transition="opacity 0.3s ease-out",s.style.opacity="0",setTimeout(()=>{s.remove(),c.querySelectorAll(".task-list-item").length===0&&(c.style.transition="opacity 0.3s ease-out",c.style.opacity="0",setTimeout(()=>c.remove(),300));let m=Array.from(h.querySelectorAll(".task-card")).find(g=>{var k;let f=(k=g.querySelector("h3"))==null?void 0:k.textContent;return f==null?void 0:f.includes(e.assignee)});if(!m){let g=e.priority||"medium";m=h.createDiv(`task-card ${g}-card`),m.createDiv("card-header").createEl("h3",{text:`\u{1F464} ${e.assignee}`,cls:"card-assignee-title"}),m.createEl("ul",{cls:"task-list"})}let u=m.querySelector(".task-list");if(u){let g=u.createEl("li",{cls:"task-list-item"});g.innerHTML=s.innerHTML;let f=g.querySelector(".task-metadata");f&&(f.innerHTML=f.innerHTML.replace(/👤\s*[^<]*/g,`\u{1F464} ${e.assignee}`));let k=g.querySelector(".task-checkbox");k&&(k.onclick=async()=>{await this.toggleTask(e,k.checked,g)});let p=g.querySelector(".edit-button");if(p){let w=g.querySelector(".edit-container");if(w){p.onclick=()=>{w.style.display=w.style.display==="none"?"flex":"none"};let C=w.querySelector("select");C&&(C.onchange=async()=>{await this.updateTaskPriority(e,C.value,g)});let x=w.querySelector("button"),A=w.querySelector("input");x&&A&&(x.onclick=async()=>{await this.updateTaskAssignee(e,A.value,g)})}}g.style.opacity="0",setTimeout(()=>{g.style.transition="opacity 0.3s ease-in",g.style.opacity="1"},10)}this.updateStatsOnly()},300))}else s||setTimeout(()=>this.refresh(),500)}catch(i){console.error("Failed to update task assignee:",i),new S.Notice("Failed to update assignee. Please try again.")}}applyFilter(e){let t=this.containerEl.querySelectorAll(".task-section"),s=e==="delegated";t.forEach(i=>{if(!(i instanceof HTMLElement))return;let a=i.querySelectorAll(".task-card"),o=!1;a.forEach(l=>{var c,h,d,m;if(!(l instanceof HTMLElement))return;let r=!0;switch(e){case"all":r=!0;break;case"high":r=l.hasClass("high-card");break;case"medium":r=l.hasClass("medium-card");break;case"low":r=l.hasClass("low-card");break;case"completed":r=l.hasClass("completed-card");break;case"mine":let u=l.querySelector("h3");if(u&&u.textContent){let g=u.textContent.replace(/^👤\s*/,"").trim().toLowerCase(),f=(m=(d=(h=(c=this.plugin)==null?void 0:c.settings)==null?void 0:h.dashboardMyName)==null?void 0:d.toLowerCase())==null?void 0:m.trim();f?r=f.split(",").map(p=>p.trim()).filter(p=>p.length>0).some(p=>g===p||g.includes(p)):r=!1}else r=!1;break;case"overdue":r=this.hasTasksOverdue(l);break;case"today":r=this.hasTasksDueToday(l);break;case"week":r=this.hasTasksDueThisWeek(l);break;case"delegated":r=this.hasDelegatedTasks(l);break}l.style.display=r?"block":"none",r&&(o=!0)}),i.style.display=o?"block":"none"})}applyMultipleFilters(){let e=this.containerEl.querySelectorAll(".task-section");if(this.activeFilters.size===0){e.forEach(t=>{t instanceof HTMLElement&&(t.style.display="block",t.querySelectorAll(".task-card").forEach(i=>{i instanceof HTMLElement&&(i.style.display="block")}))});return}e.forEach(t=>{if(!(t instanceof HTMLElement))return;let s=t.querySelectorAll(".task-card"),i=!1;s.forEach(a=>{if(!(a instanceof HTMLElement))return;let o=!1;for(let l of this.activeFilters){let r=!1;switch(l){case"high":r=a.hasClass("high-card");break;case"medium":r=a.hasClass("medium-card");break;case"low":r=a.hasClass("low-card");break;case"completed":r=a.hasClass("completed-card");break;case"overdue":r=this.hasTasksOverdue(a);break;case"today":r=this.hasTasksDueToday(a);break;case"week":r=this.hasTasksDueThisWeek(a);break;case"delegated":r=this.hasDelegatedTasks(a);break}if(r){o=!0;break}}a.style.display=o?"block":"none",o&&(i=!0)}),t.style.display=i?"block":"none"})}applyMultipleFiltersToClusters(){let e=this.containerEl.querySelectorAll(".cluster-card"),t=this.containerEl.querySelectorAll(".standalone-section .task-card");if(this.activeFilters.size===0){e.forEach(i=>{i instanceof HTMLElement&&(i.style.display="block")}),t.forEach(i=>{i instanceof HTMLElement&&(i.style.display="block")});return}Array.from(e).concat(Array.from(t)).forEach(i=>{if(!(i instanceof HTMLElement))return;let a=!1;for(let o of this.activeFilters){let l=!1;switch(o){case"high":l=i.hasClass("high-card");break;case"medium":l=i.hasClass("medium-card");break;case"low":l=i.hasClass("low-card");break;case"completed":l=i.hasClass("completed-card");break;case"overdue":l=this.hasTasksOverdue(i);break;case"today":l=this.hasTasksDueToday(i);break;case"week":l=this.hasTasksDueThisWeek(i);break;case"delegated":l=this.hasDelegatedTasks(i);break}if(l){a=!0;break}}i.style.display=a?"block":"none"})}hasTasksDueToday(e){var a;if(e.classList.contains("completed-card"))return!1;let t=e.querySelectorAll(".task-list-item"),s=new Date;s.setHours(0,0,0,0);let i=new Date(s);i.setDate(i.getDate()+1);for(let o of Array.from(t)){let l=o.querySelector(".task-checkbox");if(l&&l.checked)continue;let r=o.querySelector(".task-due");if(r){let c=(a=r.textContent)==null?void 0:a.match(/\d{4}-\d{2}-\d{2}/);if(c){let h=new Date(c[0]+"T00:00:00");if(h.setHours(0,0,0,0),h>=s&&h<i)return!0}}}return!1}hasTasksDueThisWeek(e){var a;if(e.classList.contains("completed-card"))return!1;let t=e.querySelectorAll(".task-list-item"),s=new Date;s.setHours(0,0,0,0);let i=new Date(s);i.setDate(i.getDate()+7);for(let o of Array.from(t)){let l=o.querySelector(".task-checkbox");if(l&&l.checked)continue;let r=o.querySelector(".task-due");if(r){let c=(a=r.textContent)==null?void 0:a.match(/\d{4}-\d{2}-\d{2}/);if(c){let h=new Date(c[0]+"T00:00:00");if(h.setHours(0,0,0,0),h>=s&&h<=i)return!0}}}return!1}hasTasksOverdue(e){var i;if(e.classList.contains("completed-card"))return!1;let t=e.querySelectorAll(".task-list-item"),s=new Date;s.setHours(0,0,0,0);for(let a of Array.from(t)){let o=a.querySelector(".task-checkbox");if(o&&o.checked)continue;let l=a.querySelector(".task-due");if(l){let r=(i=l.textContent)==null?void 0:i.match(/\d{4}-\d{2}-\d{2}/);if(r){let c=new Date(r[0]+"T00:00:00");if(c.setHours(0,0,0,0),c<s)return!0}}}return!1}hasDelegatedTasks(e){var o,l,r,c,h;if(e.classList.contains("completed-card"))return!1;let t=(l=(o=e.querySelector("h3"))==null?void 0:o.textContent)==null?void 0:l.replace(/^👤\s*/,"").trim();if(!t)return!1;let s=this.allTasks.filter(d=>d.assignee===t&&!d.completed),i=(h=(c=(r=this.plugin)==null?void 0:r.settings)==null?void 0:c.dashboardMyName)==null?void 0:h.toLowerCase(),a=i?i.split(",").map(d=>d.trim()).filter(d=>d.length>0):[];for(let d of s)if(d.delegatedFrom){if(a.length===0)return!0;let m=d.delegatedFrom.toLowerCase();if(a.some(g=>m===g||m.includes(g)||g.includes(m)))return!0}return!1}updateStatsOnly(){}getFilteredTasks(){var e,t;if((t=(e=this.plugin)==null?void 0:e.settings)!=null&&t.dashboardMyName){let s=this.plugin.settings.dashboardMyName.split(",").map(i=>i.toLowerCase().trim()).filter(i=>i.length>0);return s.length===0?this.allTasks:this.allTasks.filter(i=>{let a=i.assignee.toLowerCase().trim();return s.some(o=>a===o||a.includes(o))})}return this.allTasks}calculateFilterCounts(e){var h,d,m;let t=new Date,s=new Date(t.getFullYear(),t.getMonth(),t.getDate()),i=new Date(s.getTime()+1440*60*1e3-1),a=new Date(s.getTime()+10080*60*1e3),o={high:0,medium:0,low:0,today:0,week:0,overdue:0,completed:0,delegated:0},l=(m=(d=(h=this.plugin)==null?void 0:h.settings)==null?void 0:d.dashboardMyName)==null?void 0:m.toLowerCase(),r=l?l.split(",").map(u=>u.trim()).filter(u=>u.length>0):[];console.log("[TaskDashboard] Calculating filter counts. My names:",r);let c=this.allTasks;for(let u of e){if(!u.completed&&(u.priority==="high"?o.high++:u.priority==="medium"?o.medium++:u.priority==="low"&&o.low++,u.dueDate)){let g=new Date(u.dueDate);g<s?o.overdue++:g>=s&&g<=i&&o.today++,g>=s&&g<=a&&o.week++}u.completed&&o.completed++}for(let u of c)if(!u.completed&&u.delegatedFrom)if(console.log("[TaskDashboard] Found delegated task:",u.text,"delegatedFrom:",u.delegatedFrom),r.length>0){let g=u.delegatedFrom.toLowerCase(),f=r.some(k=>{let p=g===k||g.includes(k)||k.includes(g);return console.log('[TaskDashboard] Checking if "'+g+'" matches "'+k+'": '+p),p});console.log("[TaskDashboard] isDelegatedByMe:",f),f&&o.delegated++}else o.delegated++;return console.log("[TaskDashboard] Delegated count:",o.delegated),o}getCurrentFilterCounts(){let e=this.getFilteredTasks();return this.calculateFilterCounts(e)}updateFilterCountsImmediate(){let e=this.getCurrentFilterCounts();this.filterCounts=e;let t=(s,i)=>{let a=this.badgeElements.get(s);if(a)i>0?(a.textContent=i.toString(),a.style.display="inline-flex"):a.style.display="none";else if(i>0){let o=this.containerEl.querySelector(`[data-filter="${this.getDataAttr(s)}"]`);if(o){let l=this.createBadgeElement(i,this.getDataAttr(s));l&&(o.appendChild(l),this.badgeElements.set(s,l))}}};t("high",e.high),t("medium",e.medium),t("low",e.low),t("overdue",e.overdue),t("today",e.today),t("week",e.week),t("delegated",e.delegated),t("completed",e.completed)}updateFilterCounts(e=!1){if(e){this.updateFilterCountsImmediate();return}this.updateCountsDebounceTimer&&clearTimeout(this.updateCountsDebounceTimer),this.updateCountsDebounceTimer=setTimeout(()=>{this.updateFilterCountsImmediate(),this.updateCountsDebounceTimer=null},150)}getDataAttr(e){return{high:"high",medium:"medium",low:"low",overdue:"overdue",today:"due-today",week:"due-week",delegated:"delegated",completed:"completed"}[e]||e}async updateTaskDisplay(){try{let e=this.containerEl.children[1];e.querySelectorAll(".task-section").forEach(i=>i.remove());let s=this.currentFilter==="delegated"?this.allTasks:this.getFilteredTasks();await this.displayTasks(e,s),this.currentFilter!=="all"&&this.applyFilter(this.currentFilter)}catch(e){console.error("Failed to update task display:",e),new S.Notice("Failed to update display. Please refresh.")}}async loadAllParticipants(){if(this.cachedParticipants)return this.cachedParticipants;try{let e=new Set,t=this.app.vault.getMarkdownFiles();for(let s of t)try{let i=await this.app.vault.read(s),a=i.match(/^---\n([\s\S]*?)\n---/);if(!a||!a[1].includes("emailId:"))continue;let l=i.match(/## Participants\s*\n((?:- .+\n?)+)/);l&&l[1].split(`
`).forEach(h=>{let d=h.match(/^- (.+)$/);if(d){let m=d[1].trim();m.length>0&&m.length<50&&!m.includes("http")&&!m.includes("www.")&&/^[a-zA-Z\s\-\.]+$/.test(m)&&e.add(m)}});let r=i.matchAll(/\[\[@?([^\]]+)\]\]/g);for(let c of r){let h=c[1].trim();h!=="Unassigned"&&h.length<50&&/^[a-zA-Z\s\-\.]+$/.test(h)&&e.add(h)}}catch(i){continue}return this.cachedParticipants=Array.from(e).sort(),console.log("[TaskDashboard] Loaded participants:",this.cachedParticipants),this.cachedParticipants}catch(e){return console.error("[TaskDashboard] Failed to load participants:",e),[]}}applyDashboardStyles(){}async clusterCurrentTasks(e=!1){var i,a,o,l;let t=this.getFilteredTasks(),s=t.filter(r=>!r.completed);if(console.log("[Clustering] Starting clustering..."),console.log("[Clustering] Force mode:",e),console.log("[Clustering] My tasks count:",t.length),console.log("[Clustering] Incomplete tasks:",s.length),e){if(console.log("[Clustering] Force mode - clustering all tasks"),s.length<2){new S.Notice("Need at least 2 incomplete tasks to cluster");return}}else{let r=s.filter(c=>!c.clusterId);if(console.log("[Clustering] Smart mode - unclustered tasks:",r.length),r.length<2){new S.Notice("No new tasks to cluster. All tasks are already clustered or there are fewer than 2 new tasks.");return}s=r}if(!this.taskClusterer&&((a=(i=this.plugin)==null?void 0:i.settings)!=null&&a.anthropicApiKey)&&(this.taskClusterer=new B(this.plugin.settings.anthropicApiKey,this.plugin.settings.claudeModel||"claude-3-5-haiku-20241022")),!this.taskClusterer){new S.Notice("Claude API key required for clustering. Configure in settings.");return}try{let r=e?"Force":"Smart";new S.Notice(`\u{1F9E9} ${r} Re-clustering: Loading ${s.length} tasks...`),console.log("[Clustering] Calling taskClusterer.clusterTasks()...");let c=h=>{new S.Notice(`\u{1F50D} ${r} Re-clustering: ${h}`)};this.clusteringResult=await this.taskClusterer.clusterTasks(s,this.app.vault,c),console.log("[Clustering] Clustering complete. Result:",this.clusteringResult),console.log("[Clustering] Clusters created:",((o=this.clusteringResult)==null?void 0:o.clusters.length)||0),console.log("[Clustering] Standalone tasks:",((l=this.clusteringResult)==null?void 0:l.standalone.length)||0),new S.Notice(`\u{1F4BE} ${r} Re-clustering: Saving ${this.clusteringResult.clusters.length} clusters to files...`),await this.saveClusterIds(this.clusteringResult,e),new S.Notice(`\u2705 ${r} Re-clustering complete!
\u{1F4E6} ${this.clusteringResult.clusters.length} clusters created
\u{1F4CB} ${this.clusteringResult.standalone.length} standalone tasks`,6e3)}catch(r){console.error("[Clustering] Clustering failed:",r),new S.Notice(`\u274C Clustering failed: ${r.message}`,8e3),this.clusteringResult=null}}async saveClusterIds(e,t=!1){try{let s=0,i=e.clusters.reduce((a,o)=>a+o.tasks.length,0);for(let a of e.clusters)for(let o of a.tasks)await this.addClusterIdToTask(o,a.id),s++,s%10===0&&new S.Notice(`\u{1F4BE} Saving cluster IDs: ${s}/${i} tasks...`);if(t){console.log("[Clustering] Force mode - clearing cluster IDs from all standalone tasks");let a=0;for(let o of e.standalone)o.clusterId&&(await this.removeClusterIdFromTask(o),a++);a>0&&new S.Notice(`\u{1F9F9} Cleared ${a} old cluster IDs from standalone tasks`)}else console.log("[Clustering] Smart mode - preserving existing cluster IDs");console.log("[Clustering] Saved cluster IDs to tasks")}catch(s){console.error("[Clustering] Failed to save cluster IDs:",s)}}async addClusterIdToTask(e,t){try{let i=(await this.app.vault.read(e.file)).split(`
`),a=i[e.line];a.includes("\u{1F9E9} cluster:")?a=a.replace(/🧩\s*cluster:[a-z0-9-]+/g,`\u{1F9E9} cluster:${t}`):a=a.trimEnd()+` \u{1F9E9} cluster:${t}`,i[e.line]=a,await this.app.vault.modify(e.file,i.join(`
`)),e.clusterId=t}catch(s){console.error("[Clustering] Failed to add cluster ID to task:",s)}}async removeClusterIdFromTask(e){try{let s=(await this.app.vault.read(e.file)).split(`
`),i=s[e.line];i=i.replace(/\s*🧩\s*cluster:[a-z0-9-]+/g,""),s[e.line]=i,await this.app.vault.modify(e.file,s.join(`
`)),e.clusterId=void 0}catch(t){console.error("[Clustering] Failed to remove cluster ID from task:",t)}}async loadCustomClusterTitles(){try{let e=await this.app.vault.adapter.read(".obsidian/plugins/meeting-tasks/cluster-titles.json"),t=JSON.parse(e);this.customClusterTitles=new Map(Object.entries(t)),console.log("[ClusterTitles] Loaded",this.customClusterTitles.size,"custom titles")}catch(e){this.customClusterTitles=new Map}}async saveCustomClusterTitles(){try{let e=Object.fromEntries(this.customClusterTitles);await this.app.vault.adapter.write(".obsidian/plugins/meeting-tasks/cluster-titles.json",JSON.stringify(e,null,2)),console.log("[ClusterTitles] Saved",this.customClusterTitles.size,"custom titles")}catch(e){console.error("[ClusterTitles] Failed to save:",e)}}async setClusterTitle(e,t){if(this.customClusterTitles.set(e,t),await this.saveCustomClusterTitles(),this.clusteringResult){let s=this.clusteringResult.clusters.find(i=>i.id===e);s&&(s.title=t)}}async promptForClusterTitle(e){return new Promise(t=>{let s=new ee(this.app,e,a=>{t(a)}),i=s.onClose.bind(s);s.onClose=function(){i(),t(null)},s.open()})}async displayClusteredTasks(e){if(this.clusteringResult||(this.clusteringResult=this.buildClusteringFromSavedIds()),!this.clusteringResult){console.log("[DisplayClustered] No clusters found, falling back to normal view"),new S.Notice("No clusters found. Showing all tasks in normal view."),this.showClustered=!1,this.clusterToggleBtn&&(this.clusterToggleBtn.textContent="\u{1F9E9} Show Clustered View");let s=this.currentFilter==="delegated"?this.allTasks:this.getFilteredTasks();await this.displayTasks(e,s),this.activeFilters.size>0&&this.applyMultipleFilters();return}if(e.querySelectorAll(".task-section").forEach(s=>s.remove()),this.clusteringResult.clusters.length>0){let s=this.clusteringResult.clusters.filter(i=>i.tasks.some(a=>!a.completed));if(s.length>0){let i=e.createDiv("task-section clusters-section");i.createEl("h2",{text:`\u{1F9E9} Task Clusters (${s.length})`,cls:"clusters-header"});for(let a of this.clusteringResult.clusters)await this.createClusterCard(i,a)}}if(this.clusteringResult.standalone.length>0){let s=e.createDiv("task-section standalone-section");s.createEl("h2",{text:`\u{1F4CB} Standalone Tasks (${this.clusteringResult.standalone.length})`}),await this.displayTasks(s,this.clusteringResult.standalone)}}buildClusteringFromSavedIds(){try{let e=new Map,t=[],s=this.getFilteredTasks();for(let a of s)a.completed||(a.clusterId?(e.has(a.clusterId)||e.set(a.clusterId,[]),e.get(a.clusterId).push(a)):t.push(a));let i=[];for(let[a,o]of e.entries()){if(o.length<2){t.push(...o);continue}let l="low";for(let c of o){if(c.priority==="high"){l="high";break}c.priority==="medium"&&l==="low"&&(l="medium")}let r=this.customClusterTitles.get(a)||this.generateClusterTitle(o);i.push({id:a,title:r,description:`${o.length} related tasks`,tasks:o,priority:l,confidence:75})}return i.length===0?null:{clusters:i,standalone:t,totalTasksAnalyzed:s.filter(a=>!a.completed).length,clustersCreated:i.length,summary:`Loaded ${i.length} saved clusters`}}catch(e){return console.error("[Clustering] Failed to build clusters from saved IDs:",e),null}}generateClusterTitle(e){if(e.length===0)return"Related Tasks";let s=e[0].text.split(" ").filter(i=>i.length>3);return s.length>0?s.slice(0,4).join(" ")+(s.length>4?"...":""):"Related Tasks"}applyFilterToClusters(e){let t=this.containerEl.querySelectorAll(".cluster-card"),s=this.containerEl.querySelector(".standalone-section");t.forEach(i=>{if(!(i instanceof HTMLElement))return;let a=i.querySelectorAll(".cluster-task-item"),o=!1;a.forEach(l=>{if(!(l instanceof HTMLElement))return;let r=this.shouldShowTaskInFilter(l,e);l.style.display=r?"list-item":"none",r&&(o=!0)}),i.style.display=o?"block":"none"}),s instanceof HTMLElement&&this.applyFilter(e)}shouldShowTaskInFilter(e,t){var s,i,a,o;switch(t){case"all":return!0;case"high":return((s=e.closest(".cluster-card"))==null?void 0:s.classList.contains("high-card"))||!1;case"medium":return((i=e.closest(".cluster-card"))==null?void 0:i.classList.contains("medium-card"))||!1;case"low":return((a=e.closest(".cluster-card"))==null?void 0:a.classList.contains("low-card"))||!1;case"overdue":case"today":case"week":let l=e.querySelector(".task-due");if(!l)return!1;let r=(o=l.textContent)==null?void 0:o.match(/\d{4}-\d{2}-\d{2}/);if(!r)return!1;let c=new Date(r[0]),h=new Date;if(h.setHours(0,0,0,0),t==="overdue")return c<h;if(t==="today"){let d=new Date(h);return d.setDate(d.getDate()+1),c>=h&&c<d}else if(t==="week"){let d=new Date(h);return d.setDate(d.getDate()+7),c>=h&&c<=d}return!1;default:return!0}}async createClusterCard(e,t){let s=t.tasks.filter(u=>!u.completed);if(s.length===0)return;let i=e.createDiv(`cluster-card ${t.priority}-card`),a=i.createDiv("cluster-header"),o=a.createDiv("cluster-title-row"),l=o.createEl("h3",{text:`${t.title} (${s.length} tasks)`,cls:"cluster-title"}),r=o.createEl("button",{text:"\u270F\uFE0F",cls:"cluster-edit-btn",title:"Edit cluster title"});r.onclick=async u=>{u.stopPropagation(),u.preventDefault(),console.log("[ClusterTitle] Edit button clicked for cluster:",t.id);let g=t.title;console.log("[ClusterTitle] Current title:",g);let f=await this.promptForClusterTitle(g);console.log("[ClusterTitle] New title:",f),f&&f!==g&&(console.log("[ClusterTitle] Updating title..."),await this.setClusterTitle(t.id,f),l.setText(`${f} (${s.length} tasks)`),new S.Notice(`\u2705 Cluster title updated to: ${f}`))};let c=o.createEl("span",{text:`${t.confidence}%`,cls:"cluster-confidence"});if(t.confidence>=80?c.addClass("high-confidence"):t.confidence>=60?c.addClass("medium-confidence"):c.addClass("low-confidence"),a.createEl("p",{text:t.description,cls:"cluster-description"}),t.combinedTask){let u=a.createDiv("cluster-suggestion");u.createEl("strong",{text:"\u{1F4A1} Suggested Combined Task:"}),u.createEl("p",{text:t.combinedTask,cls:"combined-task-text"}),t.suggestedAssignee&&u.createEl("span",{text:`\u2192 ${t.suggestedAssignee}`,cls:"suggested-assignee"})}let h=i.createDiv("task-grid"),d={};s.forEach(u=>{let g=u.assignee||"Unassigned";d[g]||(d[g]=[]),d[g].push(u)});let m=Object.keys(d).sort((u,g)=>u.localeCompare(g));for(let u of m){let g=h.createDiv(`task-card ${t.priority}-card`);g.createDiv("card-header").createEl("h3",{text:`\u{1F464} ${u}`,cls:"card-assignee-title"});let k=g.createEl("ul",{cls:"task-list"});for(let p of d[u]){let w=k.createEl("li",{cls:"task-list-item"}),C=w.createEl("input",{type:"checkbox",cls:"task-checkbox"});C.checked=p.completed,C.onclick=async()=>{await this.toggleTask(p,C.checked,w)};let x=w.createDiv("task-content"),A=x.createEl("span",{text:p.text,cls:p.completed?"task-text completed clickable":"task-text clickable"});A.onclick=async()=>{await this.app.workspace.getLeaf(!1).openFile(p.file)};let v=x.createDiv("task-meta"),b=v.createEl("span",{cls:"task-source clickable",text:`\u{1F4C4} ${p.file.basename}`});if(b.onclick=A.onclick,b.title=`Click to open: ${p.file.basename}`,p.dueDate){let T=v.createEl("span",{cls:"task-due"});T.setText(`\u{1F4C5} ${p.dueDate}`),!p.completed&&new Date(p.dueDate)<new Date&&T.addClass("overdue")}if(p.category&&v.createEl("span",{text:`#${p.category}`,cls:"task-category"}),p.confidence&&p.confidence<70&&v.createEl("span",{text:`\u26A0\uFE0F ${p.confidence}%`,cls:"task-confidence"}),p.delegatedFrom){let T=v.createEl("span",{cls:"task-tag task-delegated-tag",text:"DELEGATED",title:`Delegated from ${p.delegatedFrom}${p.delegatedDate?" on "+p.delegatedDate:""}`}),E=v.createEl("span",{cls:"task-delegation",title:`Delegated from ${p.delegatedFrom}${p.delegatedDate?" on "+p.delegatedDate:""}`});E.setText(`\u{1F517} from @${p.delegatedFrom}`),p.delegatedDate&&E.setText(`\u{1F517} from @${p.delegatedFrom} \u{1F4C6} ${p.delegatedDate}`)}if(p.delegationChain&&p.delegationChain.length>0){let T=v.createEl("span",{cls:"task-delegation-chain",title:"Delegation history"}),E=p.delegationChain.map(D=>`@${D.assignee} (${D.date})`).join(" \u2192 ");T.setText(`\u{1F517} Chain: ${E}`)}}}}};var z=require("obsidian"),W=class{constructor(n,e){this.getStoredToken=n;this.saveToken=e;this.credentials=null;this.token=null;this.baseUrl="https://gmail.googleapis.com/gmail/v1";this.authBaseUrl="https://accounts.google.com/o/oauth2";this.tokenUrl="https://oauth2.googleapis.com/token";this.redirectUri="http://localhost";this.token=this.getStoredToken()}setCredentials(n,e,t){this.redirectUri=t||"http://localhost",this.credentials={client_id:n,client_secret:e,redirect_uri:this.redirectUri}}getAuthorizationUrl(){if(!this.credentials)throw new Error("Credentials not set. Please configure Google OAuth in settings.");let n=new URLSearchParams({client_id:this.credentials.client_id,redirect_uri:this.redirectUri,response_type:"code",scope:"https://www.googleapis.com/auth/gmail.readonly",access_type:"offline",prompt:"consent"});return`${this.authBaseUrl}/auth?${n.toString()}`}async exchangeCodeForToken(n){if(!this.credentials)throw new Error("Credentials not set");try{let e=await(0,z.requestUrl)({url:this.tokenUrl,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({code:n,client_id:this.credentials.client_id,client_secret:this.credentials.client_secret,redirect_uri:this.redirectUri,grant_type:"authorization_code"}).toString()});if(e.status===200){let t=e.json;this.token={access_token:t.access_token,refresh_token:t.refresh_token,expiry_date:Date.now()+t.expires_in*1e3,token_type:t.token_type,scope:t.scope},await this.saveToken(this.token)}else throw new Error(`Failed to exchange code: ${e.text}`)}catch(e){throw console.error("OAuth token exchange failed:",e),e}}async refreshAccessToken(){var n;if(!this.credentials||!((n=this.token)!=null&&n.refresh_token))throw new Error("Cannot refresh token: missing credentials or refresh token");try{let e=await(0,z.requestUrl)({url:this.tokenUrl,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({refresh_token:this.token.refresh_token,client_id:this.credentials.client_id,client_secret:this.credentials.client_secret,grant_type:"refresh_token"}).toString()});if(e.status===200){let t=e.json;this.token={...this.token,access_token:t.access_token,expiry_date:Date.now()+t.expires_in*1e3},await this.saveToken(this.token)}else throw new Error(`Failed to refresh token: ${e.text}`)}catch(e){throw console.error("Token refresh failed:",e),e}}async ensureValidToken(){if(!this.token)throw new Error("Not authenticated. Please authenticate with Gmail first.");this.token.expiry_date&&Date.now()>=this.token.expiry_date-6e4&&(console.log("[Gmail] Token expired or expiring soon, refreshing..."),await this.refreshAccessToken())}async makeGmailRequest(n,e={}){await this.ensureValidToken();let t=n.startsWith("http")?n:`${this.baseUrl}${n}`;try{let s=await(0,z.requestUrl)({url:t,method:e.method||"GET",headers:{Authorization:`Bearer ${this.token.access_token}`,"Content-Type":"application/json",...e.headers},...e});return s.status===401?(console.log("[Gmail] Received 401, attempting token refresh..."),await this.refreshAccessToken(),(await(0,z.requestUrl)({url:t,method:e.method||"GET",headers:{Authorization:`Bearer ${this.token.access_token}`,"Content-Type":"application/json",...e.headers},...e})).json):s.json}catch(s){throw console.error(`Gmail API request failed: ${n}`,s),s}}async searchEmails(n,e=100,t=5,s=!0){try{console.log(`[Gmail] Searching with query: ${n}, max: ${e}`);let i=[],a,o=0,l=1;for(;o<e;){let d=Math.min(e-o,500),m=`/users/me/messages?q=${encodeURIComponent(n)}&maxResults=${d}`;a?(m+=`&pageToken=${a}`,console.log(`[Gmail] Fetching page ${l} with token: ${a.substring(0,10)}...`)):console.log(`[Gmail] Fetching page ${l} (first page), requesting ${d} messages`);let u=await this.makeGmailRequest(m);if(!u.messages||u.messages.length===0){if(o===0)return console.log("[Gmail] No messages found"),[];console.log(`[Gmail] No more messages on page ${l}`);break}if(i.push(...u.messages),o+=u.messages.length,console.log(`[Gmail] Page ${l}: Got ${u.messages.length} message IDs (total so far: ${o}/${e})`),u.nextPageToken&&o<e)a=u.nextPageToken,console.log("[Gmail] Next page token available, will fetch more..."),l++;else{u.nextPageToken?console.log(`[Gmail] Reached desired maxResults of ${e}`):console.log("[Gmail] No more pages available (no nextPageToken)");break}}if(console.log(`[Gmail] Total message IDs collected: ${i.length}`),i.length===0)return[];let r=i.slice(0,e).filter(d=>d.id),c=Math.ceil(r.length/t);console.log(`[Gmail] Starting parallel fetch: ${r.length} emails in ${c} batches`);let h=[];for(let d=0;d<r.length;d+=t){let m=r.slice(d,d+t),u=Math.floor(d/t)+1;console.log(`[Gmail] Fetching batch ${u}/${c} (${m.length} emails in parallel)...`);let g=Date.now(),f=m.map(C=>(console.log(`[Gmail] Starting fetch for email ${C.id}`),this.getEmailById(C.id).catch(x=>(console.error(`[Gmail] Failed to fetch message ${C.id}:`,x),null)))),p=(await Promise.all(f)).filter(C=>C!==null);h.push(...p);let w=Date.now()-g;console.log(`[Gmail] Batch ${u} complete: ${p.length}/${m.length} successful in ${w}ms`)}return console.log(`[Gmail] All batches complete: ${h.length} emails fetched successfully`),s&&h.length>0&&(h.sort((d,m)=>{let u=new Date(d.date||0).getTime();return new Date(m.date||0).getTime()-u}),console.log(`[Gmail] Sorted ${h.length} emails by date (newest first)`)),h}catch(i){throw console.error("Email search failed:",i),i}}async getEmailById(n){var e,t,s;try{let i=await this.makeGmailRequest(`/users/me/messages/${n}?format=full`),a=((e=i.payload)==null?void 0:e.headers)||[],o=h=>{let d=a.find(m=>{var u;return((u=m.name)==null?void 0:u.toLowerCase())===h.toLowerCase()});return(d==null?void 0:d.value)||""},l=this.extractBody(i.payload),r=[];if((t=i.payload)!=null&&t.parts)for(let h of i.payload.parts)h.filename&&((s=h.body)!=null&&s.attachmentId)&&r.push({filename:h.filename,mimeType:h.mimeType,size:h.body.size,attachmentId:h.body.attachmentId,downloadUrl:`https://mail.google.com/mail/u/0/?ui=2&ik=${n}&attid=${h.body.attachmentId}&disp=safe&zw`});let c=`https://mail.google.com/mail/u/0/#inbox/${n}`;return{id:n,threadId:i.threadId,subject:o("subject"),from:o("from"),to:o("to"),date:o("date"),body:l,snippet:i.snippet||"",attachments:r,gmailUrl:c,labels:i.labelIds||[]}}catch(i){throw console.error(`Failed to get email ${n}:`,i),i}}extractBody(n){var e,t,s;if(!n)return"";if((e=n.body)!=null&&e.data)return atob(n.body.data.replace(/-/g,"+").replace(/_/g,"/"));if(n.parts){for(let i of n.parts)if(i.mimeType==="text/plain"&&((t=i.body)!=null&&t.data))return atob(i.body.data.replace(/-/g,"+").replace(/_/g,"/"));for(let i of n.parts)if(i.mimeType==="text/html"&&((s=i.body)!=null&&s.data))return atob(i.body.data.replace(/-/g,"+").replace(/_/g,"/")).replace(/<[^>]*>/g,"").trim();for(let i of n.parts){let a=this.extractBody(i);if(a)return a}}return""}async fetchRecentMeetingEmails(n,e){let t=new Date;t.setTime(t.getTime()-n*60*60*1e3);let s=t.toISOString().split("T")[0],i=(e||"transcript").split(",").map(l=>l.trim().toLowerCase()).filter(l=>l);console.log(`[Gmail] Searching for emails with labels: ${i.join(", ")} after ${s} (${n} hours back)`);let a=new Map;for(let l of i){let r=`label:${l} after:${s}`;console.log(`[Gmail] Searching with query: "${r}"`);let c=await this.searchEmails(r,500,5,!0);console.log(`[Gmail] Found ${c.length} emails with label: ${l}`);for(let h of c)if(a.has(h.id)){let d=a.get(h.id);d.searchedLabels||(d.searchedLabels=[]),d.searchedLabels.includes(l)||d.searchedLabels.push(l)}else h.searchedLabels=[l],a.set(h.id,h)}let o=Array.from(a.values());return console.log(`[Gmail] Total unique emails found: ${o.length}`),o}isAuthenticated(){var n;return!!((n=this.token)!=null&&n.access_token)}hasRefreshToken(){var n;return!!((n=this.token)!=null&&n.refresh_token)}async testConnection(){try{await this.ensureValidToken();let n=await this.makeGmailRequest("/users/me/profile");return console.log("[Gmail] Connection test successful:",n.emailAddress),!0}catch(n){return console.error("[Gmail] Connection test failed:",n),!1}}clearAuthentication(){this.token=null}};var _=class{constructor(){this.server=null;this.port=3e3;this.authCodePromise=null;this.authCodeResolve=null;this.authCodeReject=null}async start(){if(!this.server)return new Promise((n,e)=>{try{let t=window.require("http");this.server=t.createServer((s,i)=>{let a=new URL(s.url,`http://localhost:${this.port}`);if(a.pathname==="/callback"){let o=a.searchParams.get("code"),l=a.searchParams.get("error");i.writeHead(200,{"Content-Type":"text/html"}),o?(i.end(`
<!DOCTYPE html>
<html>
<head>
<title>Authentication Successful</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
backdrop-filter: blur(10px);
}
.success-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
h1 {
margin: 0 0 0.5rem 0;
font-size: 2rem;
}
p {
margin: 0.5rem 0;
opacity: 0.9;
font-size: 1.1rem;
}
.close-hint {
margin-top: 2rem;
font-size: 0.9rem;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="container">
<div class="success-icon">\u2705</div>
<h1>Authentication Successful!</h1>
<p>You can now close this window and return to Obsidian.</p>
<p class="close-hint">This window will close automatically in 3 seconds...</p>
</div>
<script>
setTimeout(() => window.close(), 3000);
<\/script>
</body>
</html>
`),this.authCodeResolve&&(this.authCodeResolve(o),this.authCodeResolve=null,this.authCodeReject=null)):(i.end(`
<!DOCTYPE html>
<html>
<head>
<title>Authentication Failed</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #f5576c 0%, #f093fb 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
backdrop-filter: blur(10px);
}
.error-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
h1 {
margin: 0 0 0.5rem 0;
font-size: 2rem;
}
p {
margin: 0.5rem 0;
opacity: 0.9;
}
.error-msg {
margin-top: 1rem;
padding: 1rem;
background: rgba(0, 0, 0, 0.2);
border-radius: 6px;
font-family: monospace;
font-size: 0.9rem;
}
</style>
</head>
<body>
<div class="container">
<div class="error-icon">\u274C</div>
<h1>Authentication Failed</h1>
<p>There was an error during authentication.</p>
${l?`<div class="error-msg">Error: ${l}</div>`:""}
<p>Please close this window and try again.</p>
</div>
</body>
</html>
`),this.authCodeReject&&(this.authCodeReject(new Error(l||"Authentication failed")),this.authCodeResolve=null,this.authCodeReject=null))}else i.writeHead(404),i.end("Not found")}),this.server.listen(this.port,"127.0.0.1",()=>{console.log(`[OAuth Server] Started on http://127.0.0.1:${this.port}`),n()}),this.server.on("error",s=>{s.code==="EADDRINUSE"?(console.error(`[OAuth Server] Port ${this.port} is already in use`),e(new Error(`Port ${this.port} is already in use. Please close any other applications using this port.`))):e(s)})}catch(t){e(t)}})}async stop(){if(this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null),this.authCodeResolve=null,this.authCodeReject=null,this.authCodePromise=null,this.server)return new Promise(n=>{this.server.close(()=>{console.log("[OAuth Server] Stopped"),this.server=null,n()})})}async waitForAuthCode(){if(!this.server)throw new Error("OAuth server not started");return this.authCodeResolve=null,this.authCodeReject=null,this.authCodePromise=null,this.authCodePromise=new Promise((n,e)=>{this.authCodeResolve=n,this.authCodeReject=e;let t=setTimeout(()=>{this.authCodeResolve=null,this.authCodeReject=null,e(new Error("OAuth timeout - no response received within 5 minutes"))},300*1e3);this.timeoutId=t}),this.authCodePromise}getRedirectUri(){return`http://127.0.0.1:${this.port}/callback`}isRunning(){return this.server!==null}};var N=require("obsidian"),K=class{constructor(n,e){this.codeVerifier="";this.state="";this.clientId=n,this.clientSecret=e}generateRandomString(n){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t="",s=new Uint8Array(n);crypto.getRandomValues(s);for(let i=0;i<n;i++)t+=e[s[i]%e.length];return t}async sha256(n){let t=new TextEncoder().encode(n),s=await crypto.subtle.digest("SHA-256",t);return btoa(String.fromCharCode(...new Uint8Array(s))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async authenticate(){this.codeVerifier=this.generateRandomString(128),this.state=this.generateRandomString(32);let n=await this.sha256(this.codeVerifier),e=new URL("https://accounts.google.com/o/oauth2/v2/auth");return e.searchParams.set("client_id",this.clientId),e.searchParams.set("redirect_uri","obsidian://meeting-tasks-callback"),e.searchParams.set("response_type","code"),e.searchParams.set("scope","https://www.googleapis.com/auth/gmail.readonly"),e.searchParams.set("state",this.state),e.searchParams.set("code_challenge",n),e.searchParams.set("code_challenge_method","S256"),e.searchParams.set("access_type","offline"),e.searchParams.set("prompt","consent"),console.log("[Mobile OAuth] Starting authentication flow..."),new N.Notice("Opening browser for Gmail authentication..."),window.open(e.toString()),new Promise((t,s)=>{let i=setTimeout(()=>{s(new Error("OAuth authentication timeout (5 minutes)"))},3e5);window._oauthMobileResolver={resolve:a=>{clearTimeout(i),t(a)},reject:a=>{clearTimeout(i),s(a)}}})}async handleCallback(n){try{let e=new URL(n),t=e.searchParams.get("code"),s=e.searchParams.get("state"),i=e.searchParams.get("error");if(i)throw new Error(`OAuth error: ${i}`);if(s!==this.state)throw new Error("Invalid state parameter - possible CSRF attack");if(!t)throw new Error("No authorization code received");console.log("[Mobile OAuth] Authorization code received, exchanging for tokens...");let a=await this.exchangeCodeForTokens(t),o=window._oauthMobileResolver;o&&(o.resolve(a),delete window._oauthMobileResolver),new N.Notice("Gmail authentication successful!")}catch(e){console.error("[Mobile OAuth] Callback handling failed:",e);let t=window._oauthMobileResolver;t&&(t.reject(e),delete window._oauthMobileResolver),new N.Notice("Gmail authentication failed: "+e.message)}}async exchangeCodeForTokens(n){let e="https://oauth2.googleapis.com/token",t=new URLSearchParams({client_id:this.clientId,client_secret:this.clientSecret,code:n,code_verifier:this.codeVerifier,grant_type:"authorization_code",redirect_uri:"obsidian://meeting-tasks-callback"});console.log("[Mobile OAuth] Exchanging code for tokens...");let s=await(0,N.requestUrl)({url:e,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:t.toString()});if(s.status!==200)throw new Error(`Token exchange failed: ${s.status}`);return s.json}async refreshToken(n){let e="https://oauth2.googleapis.com/token",t=new URLSearchParams({client_id:this.clientId,client_secret:this.clientSecret,refresh_token:n,grant_type:"refresh_token"});console.log("[Mobile OAuth] Refreshing access token...");let s=await(0,N.requestUrl)({url:e,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:t.toString()});if(s.status!==200)throw new Error(`Token refresh failed: ${s.status}`);let i=s.json;return i.refresh_token||(i.refresh_token=n),i}isTokenValid(n){return Date.now()<n-300*1e3}};var te=require("obsidian"),F=class{static isMobile(){return te.Platform.isMobile||te.Platform.isMobileApp}static getMaxEmailBatch(){return this.isMobile()?25:500}static getMaxClusterTasks(){return this.isMobile()?50:Number.MAX_SAFE_INTEGER}static supportsBackgroundProcessing(){return!this.isMobile()}static getOAuthCallbackScheme(){return this.isMobile()?"obsidian://meeting-tasks-callback":"http://127.0.0.1:3000/callback"}static getDefaultLookbackTime(){return this.isMobile()?"1d":"3d"}static shouldWarnAboutNetwork(){return this.isMobile()}static getTouchTargetScale(){return this.isMobile()?1.5:1}};var G=require("obsidian"),U=class{constructor(n){this.config=n}get label(){return this.config.label}get folderName(){return this.config.folderName}canProcess(n){var e;return((e=n.searchedLabels)==null?void 0:e.includes(this.config.label))||!1}async process(n,e){try{console.log(`[${this.config.label}] Processing email ${n.id}`);let t=n.body;if(typeof t=="object"&&(t=JSON.stringify(t)),!t||t==="{}"||t==="[object Object]")return console.warn(`[${this.config.label}] No valid email content`),{success:!1};let s=await this.extractTasks(t,n.subject,e);if(await this.createNote(n,s,e)){let a=s.tasks.filter(o=>o.priority==="high").length;return{success:!0,taskCount:s.tasks.length,highPriorityCount:a,confidence:s.confidence,emailTitle:n.subject||"Untitled"}}return{success:!1}}catch(t){return console.error(`[${this.config.label}] Error:`,t),{success:!1}}}async extractTasks(n,e,t){if(!t.claudeExtractor||!t.anthropicApiKey)return console.log(`[${this.config.label}] No Claude API key, skipping extraction`),{tasks:[],summary:e||"Email note",participants:[],meetingDate:new Date,keyDecisions:[],nextSteps:[],confidence:0};console.log(`[${this.config.label}] Starting Claude extraction with prompt type: ${this.config.promptType||"default"}`);let s=Date.now(),i;this.config.promptType==="actionitem"?i=await t.claudeExtractor.extractActionItems(n,e):this.config.promptType==="meeting"||this.config.promptType==="transcript"?i=await t.claudeExtractor.extractTasks(n,e):this.config.customPrompt?i=await t.claudeExtractor.extractTasks(n,e):i=await t.claudeExtractor.extractTasks(n,e);let a=Date.now()-s;return console.log(`[${this.config.label}] Claude extraction complete in ${a}ms: ${i.tasks.length} tasks`),i}async createNote(n,e,t){try{let s=new Date().getFullYear(),i=String(new Date().getMonth()+1).padStart(2,"0"),a=(0,G.normalizePath)(t.emailNotesFolder),o=(0,G.normalizePath)(`${a}/${this.config.folderName}`),l=(0,G.normalizePath)(`${o}/${s}`),r=(0,G.normalizePath)(`${l}/${i}`);t.app.vault.getAbstractFileByPath(a)||await t.app.vault.createFolder(a),t.app.vault.getAbstractFileByPath(o)||await t.app.vault.createFolder(o),t.app.vault.getAbstractFileByPath(l)||await t.app.vault.createFolder(l),t.app.vault.getAbstractFileByPath(r)||await t.app.vault.createFolder(r);let c=new Date(n.date||Date.now()).toISOString().split("T")[0],h=(n.subject||"Email").replace(/[\\/:*?"<>|]/g,"-").substring(0,50),d=`${c} - ${h}.md`,m=(0,G.normalizePath)(`${r}/${d}`);if(t.app.vault.getAbstractFileByPath(m))return console.log(`[${this.config.label}] Note already exists: ${m}`),!1;let u=this.formatNote(n,e);return await t.app.vault.create(m,u),console.log(`[${this.config.label}] Created note: ${m}`),t.emailIdCache.add(n.id),await t.saveSettings(),!0}catch(s){return console.error(`[${this.config.label}] Error creating note:`,s),!1}}formatNote(n,e){var s,i;let t=[];if(t.push("---"),t.push(`title: ${e.summary}`),t.push(`emailId: ${n.id}`),t.push(`label: ${this.config.label}`),n.gmailUrl&&t.push(`gmailUrl: ${n.gmailUrl}`),t.push("---"),t.push(""),t.push(`# ${e.summary}`),t.push(""),t.push("## Email Details"),t.push(`**From:** ${n.from}`),t.push(`**Date:** ${n.date}`),n.gmailUrl&&t.push(`**Email:** [View in Gmail](${n.gmailUrl})`),t.push(""),n.attachments&&n.attachments.length>0){let a=n.attachments.map(o=>`${o.filename} (${this.formatFileSize(o.size)})`).join(", ");t.push(`**Attachments:** ${a}`),t.push("")}if(e.participants&&e.participants.length>0&&(t.push("## Participants"),e.participants.forEach(a=>t.push(`- ${a}`)),t.push("")),e.tasks&&e.tasks.length>0){t.push("## Action Items"),t.push("");let a=e.tasks.filter(r=>r.priority==="high"),o=e.tasks.filter(r=>r.priority==="medium"),l=e.tasks.filter(r=>r.priority==="low");a.length>0&&(t.push("### \u{1F534} High Priority"),a.forEach(r=>t.push(this.formatTask(r))),t.push("")),o.length>0&&(t.push("### \u{1F7E1} Medium Priority"),o.forEach(r=>t.push(this.formatTask(r))),t.push("")),l.length>0&&(t.push("### \u{1F7E2} Low Priority"),l.forEach(r=>t.push(this.formatTask(r))),t.push(""))}return e.nextSteps&&e.nextSteps.length>0&&(t.push("## Next Steps"),e.nextSteps.forEach(a=>{typeof a=="string"?t.push(`- [ ] ${a}`):t.push(`- [ ] ${a.description||a}`)}),t.push("")),e.keyDecisions&&e.keyDecisions.length>0&&(t.push("## Key Decisions"),e.keyDecisions.forEach(a=>t.push(`- ${a}`)),t.push("")),n.snippet&&!((s=e.keyDecisions)!=null&&s.length)&&!((i=e.nextSteps)!=null&&i.length)&&(t.push("## Context"),t.push(n.snippet),t.push("")),t.push("---"),t.push(`**[\u{1F504} Reprocess this email](obsidian://meeting-tasks-reprocess?id=${n.id})**`),t.join(`
`)}formatTask(n){let e=`- [ ] ${n.description}`;return n.assignee&&(e+=` [[${n.assignee}]]`),n.dueDate&&(e+=` \u{1F4C5} ${n.dueDate}`),n.confidence&&n.confidence<70&&(e+=` \u26A0\uFE0F ${n.confidence}%`),n.tags&&n.tags.length>0&&(e+=` ${n.tags.map(t=>`#${t}`).join(" ")}`),e}formatFileSize(n){return n<1024?n+"B":n<1024*1024?(n/1024).toFixed(1)+"KB":(n/(1024*1024)).toFixed(1)+"MB"}};var J=class{constructor(){this.processors=[]}initializeFromConfig(n){this.processors=[];for(let e of n){let t=new U(e);this.processors.push(t),console.log(`[Registry] Registered processor: ${e.label} -> ${e.folderName} (prompt: ${e.promptType||"default"})`)}}getProcessor(n){for(let e of this.processors)if(e.canProcess(n))return e;return null}getAllProcessors(){return this.processors}getProcessorByLabel(n){return this.processors.find(e=>e.label===n)||null}},j=new J;var fe={lookbackTime:"5d",debugMode:!1,anthropicApiKey:"",googleClientId:"",googleClientSecret:"",claudeModel:"claude-3-5-haiku-20241022",dashboardShowOnlyMyTasks:!0,dashboardMyName:"",gmailLabels:"transcript, action",emailNotesFolder:"TaskAgent",labelProcessors:[{label:"transcript",folderName:"Transcript",promptType:"meeting"},{label:"action",folderName:"Action",promptType:"actionitem"}],gmailToken:null,showDetailedNotifications:!0},Y=class extends y.Plugin{constructor(){super(...arguments);this.gmailService=null;this.claudeExtractor=null;this.taskClusterer=null;this.oauthServer=null;this.mobileOAuthHandler=null;this.statusBarItem=null;this.emailIdCache=new Set}parseTimeToHours(e){let t=e.match(/^(\d+(?:\.\d+)?)\s*([hdwM]?)$/);if(!t){let a=parseFloat(e);return isNaN(a)?120:a}let s=parseFloat(t[1]);switch(t[2]||"h"){case"h":return s;case"d":return s*24;case"w":return s*24*7;case"M":return s*24*30;default:return s}}formatTimeString(e){return e<24?`${e}h`:e<168?`${Math.round(e/24)}d`:e<720?`${Math.round(e/168)}w`:`${Math.round(e/720)}M`}async loadEmailIdCache(){console.log("[LoadCache] Starting to load email IDs from vault notes..."),this.emailIdCache.clear();let e=this.app.vault.getMarkdownFiles();console.log(`[LoadCache] Found ${e.length} total markdown files in vault`);let t=this.settings.emailNotesFolder;console.log(`[LoadCache] Scanning base folder: ${t}`);let s=0,i=0;for(let a of e)if(a.path.startsWith(t)){s++;try{let l=(await this.app.vault.read(a)).match(/^---\n([\s\S]*?)\n---/);if(l){let r=l[1].match(/emailId:\s*(.+)/);if(r&&r[1]){let c=r[1].trim();this.emailIdCache.add(c),i++,console.log(`[LoadCache] Found emailId: ${c} in ${a.path}`)}}}catch(o){console.error(`[LoadCache] Error reading file ${a.path}:`,o)}}console.log(`[LoadCache] Scanned ${s} email notes, found ${i} email IDs`),console.log(`[LoadCache] Cache now contains ${this.emailIdCache.size} unique email IDs`),this.settings.processedEmails=Array.from(this.emailIdCache),await this.saveSettings(),console.log(`[LoadCache] Saved ${this.settings.processedEmails.length} email IDs to settings`)}async onload(){console.log("==============================================="),console.log("Loading Meeting Tasks Plugin..."),console.log("Plugin version: 2.0.0"),console.log("===============================================");let e=await this.loadData();this.settings=Object.assign({},fe,e),this.settings.labelProcessors&&this.settings.labelProcessors.length>0?(j.initializeFromConfig(this.settings.labelProcessors),console.log(`[Plugin] Initialized ${this.settings.labelProcessors.length} label processors`)):console.warn("[Plugin] No label processors configured, using defaults"),this.settings.processedEmails&&(this.settings.processedEmails.forEach(s=>this.emailIdCache.add(s)),console.log(`[Plugin] Loaded ${this.emailIdCache.size} email IDs from settings`)),this.app.workspace.onLayoutReady(async()=>{await this.loadEmailIdCache(),console.log(`[Plugin] Found ${this.emailIdCache.size} existing meeting notes in vault`)}),this.registerEvent(this.app.vault.on("delete",async s=>{var i;if(s instanceof y.TFile&&s.extension==="md"&&s.path.startsWith(this.settings.emailNotesFolder)){console.log(`[Delete] Email note deleted: ${s.path}`);let a=this.app.metadataCache.getFileCache(s);if((i=a==null?void 0:a.frontmatter)!=null&&i.emailId){let o=a.frontmatter.emailId;console.log(`[Delete] Removing emailId from cache: ${o}`),this.emailIdCache.delete(o),this.settings.processedEmails=Array.from(this.emailIdCache),await this.saveSettings(),console.log(`[Delete] Updated cache, now contains ${this.emailIdCache.size} email IDs`)}}})),this.registerEvent(this.app.vault.on("rename",async(s,i)=>{var a,o;if(s instanceof y.TFile&&s.extension==="md"){let l=i.startsWith(this.settings.emailNotesFolder),r=s.path.startsWith(this.settings.emailNotesFolder);if(l&&!r){console.log(`[Rename] File moved out of email notes folder: ${i} -> ${s.path}`);let c=this.app.metadataCache.getFileCache(s);if((a=c==null?void 0:c.frontmatter)!=null&&a.emailId){let h=c.frontmatter.emailId;console.log(`[Rename] Removing emailId from cache: ${h}`),this.emailIdCache.delete(h),this.settings.processedEmails=Array.from(this.emailIdCache),await this.saveSettings(),console.log(`[Rename] Cache updated, now contains ${this.emailIdCache.size} email IDs`)}else console.log("[Rename] File has no emailId in frontmatter, skipping cache update")}else if(!l&&r){console.log(`[Rename] File moved into email notes folder: ${i} -> ${s.path}`),await new Promise(h=>setTimeout(h,100));let c=this.app.metadataCache.getFileCache(s);if((o=c==null?void 0:c.frontmatter)!=null&&o.emailId){let h=c.frontmatter.emailId;console.log(`[Rename] Adding emailId to cache: ${h}`),this.emailIdCache.add(h),this.settings.processedEmails=Array.from(this.emailIdCache),await this.saveSettings(),console.log(`[Rename] Cache updated, now contains ${this.emailIdCache.size} email IDs`)}else try{let d=(await this.app.vault.read(s)).match(/^---\n([\s\S]*?)\n---/);if(d){let m=d[1].match(/emailId:\s*(.+)/);if(m&&m[1]){let u=m[1].trim();console.log(`[Rename] Adding emailId to cache (from file content): ${u}`),this.emailIdCache.add(u),this.settings.processedEmails=Array.from(this.emailIdCache),await this.saveSettings(),console.log(`[Rename] Cache updated, now contains ${this.emailIdCache.size} email IDs`)}else console.log("[Rename] File has no emailId in frontmatter, not adding to cache")}else console.log("[Rename] File has no frontmatter, not adding to cache")}catch(h){console.error("[Rename] Error reading file content:",h)}}}})),this.registerObsidianProtocolHandler("meeting-tasks-reprocess",async s=>{s.id&&await this.reprocessEmailById(s.id,!0)}),this.registerObsidianProtocolHandler("meeting-tasks-oauth",async s=>{if(s.code)try{if(!this.gmailService){new y.Notice("Gmail service not initialized");return}await this.gmailService.exchangeCodeForToken(s.code),new y.Notice("\u2705 Successfully authenticated with Gmail!"),await this.initializeServices(),this.app.workspace.trigger("meeting-tasks:auth-complete")}catch(i){new y.Notice(`Authentication failed: ${i.message}`),console.error("OAuth callback error:",i)}else s.error&&new y.Notice(`Authentication failed: ${s.error}`)}),this.registerObsidianProtocolHandler("meeting-tasks-callback",async s=>{if(console.log("[Mobile OAuth] Callback received"),F.isMobile()&&this.mobileOAuthHandler){let i=`obsidian://meeting-tasks-callback?${new URLSearchParams(s).toString()}`;await this.mobileOAuthHandler.handleCallback(i)}else console.warn("[Mobile OAuth] Handler not available or not on mobile platform"),new y.Notice("Mobile OAuth not configured")}),(0,y.addIcon)("mail-check",'<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/><path d="m16 19 2 2 4-4"/></svg>');let t=this.addRibbonIcon("mail-check","Process meeting emails",async()=>{await this.processEmails()});this.statusBarItem=this.addStatusBarItem(),this.updateStatus("Ready"),this.addCommand({id:"process-meeting-emails",name:"\u{1F4E7} Process meeting emails now",callback:async()=>{await this.processEmails()},hotkeys:[{modifiers:["Mod","Shift"],key:"M"}]}),this.addCommand({id:"open-task-dashboard",name:"Open task dashboard",callback:()=>{this.openTaskDashboard()}}),this.addCommand({id:"quick-process-emails",name:"\u26A1 Quick process (last 24 hours)",callback:async()=>{let s=this.settings.lookbackTime;this.settings.lookbackTime="24h",await this.processEmails(),this.settings.lookbackTime=s}}),this.addCommand({id:"reset-processed-emails",name:"Reset processed emails",callback:async()=>{await this.resetProcessedEmails()}}),this.addCommand({id:"reprocess-meeting-note",name:"\u{1F504} Reprocess current meeting note",callback:async()=>{await this.reprocessCurrentMeetingNote()}}),this.addCommand({id:"reprocess-email-by-id",name:"\u{1F4E7} Reprocess email by ID",callback:async()=>{await this.reprocessEmailById("1995cbb7415c015f")}}),this.registerView(H,s=>new V(s,this)),this.addRibbonIcon("layout-dashboard","Open task dashboard",()=>{this.openTaskDashboard()}),await this.initializeServices(),this.addSettingTab(new se(this.app,this))}async initializeServices(){try{this.gmailService=new W(()=>this.settings.gmailToken,async e=>{this.settings.gmailToken=e,await this.saveSettings()}),this.settings.googleClientId&&this.settings.googleClientSecret?(this.gmailService.setCredentials(this.settings.googleClientId,this.settings.googleClientSecret),F.isMobile()?(console.log("[Platform] Mobile detected - using PKCE OAuth flow"),this.mobileOAuthHandler=new K(this.settings.googleClientId,this.settings.googleClientSecret)):(console.log("[Platform] Desktop detected - using local server OAuth flow"),this.oauthServer||(this.oauthServer=new _)),this.gmailService.isAuthenticated()?await this.gmailService.testConnection()?this.updateStatus("Gmail connected"):this.updateStatus("Gmail auth needed"):this.updateStatus("Gmail not authenticated")):this.updateStatus("Gmail setup needed"),this.settings.anthropicApiKey&&(this.claudeExtractor=new O(this.settings.anthropicApiKey,this.settings.claudeModel),this.taskClusterer=new B(this.settings.anthropicApiKey,this.settings.claudeModel))}catch(e){console.error("Failed to initialize services:",e),new y.Notice(`Error: ${e.message}`)}}async processEmails(){console.log("[processEmails] Starting email processing");try{let e=F.isMobile(),t=F.getMaxEmailBatch();if(e&&(console.log(`[Platform] Mobile detected - limiting to ${t} emails`),new y.Notice(`\u{1F4F1} Mobile mode: Processing limited to ${t} emails`,3e3)),this.updateStatus("\u{1F504} Starting email processing..."),new y.Notice("\u{1F4E7} Starting email processing..."),this.emailIdCache.size===0&&this.app.vault.getMarkdownFiles().length>0&&(console.log("[processEmails] Cache empty, loading from vault..."),await this.loadEmailIdCache()),!this.gmailService){this.updateStatus("\u274C Gmail service not initialized"),new y.Notice("Gmail service not initialized");return}if(!this.gmailService.isAuthenticated()){this.updateStatus("\u274C Not authenticated"),new y.Notice("Please authenticate with Gmail first (see plugin settings)");return}let s=this.parseTimeToHours(this.settings.lookbackTime);this.updateStatus(`\u{1F50D} Searching emails (${this.settings.lookbackTime})...`),new y.Notice(`\u{1F504} Searching for meeting emails from the last ${this.settings.lookbackTime}...`);let i=await this.gmailService.fetchRecentMeetingEmails(s,this.settings.gmailLabels);if(i.length===0){this.updateStatus("\u2705 No new emails found"),new y.Notice(`\u2705 No meeting emails found in the last ${this.settings.lookbackTime}`);return}this.updateStatus(`\u{1F4CA} Found ${i.length} emails`),new y.Notice(`\u{1F4CA} Found ${i.length} meeting emails. Processing...`),i.length>0&&(console.log("[Process] Emails sorted by date (newest first):"),i.slice(0,5).forEach((u,g)=>{var f;console.log(`[Process] ${g+1}. ${u.date} - ${((f=u.subject)==null?void 0:f.substring(0,50))||"No subject"}`)}),i.length>5&&console.log(`[Process] ... and ${i.length-5} more emails`));let a=0,o=0,l=0,r=0,c=0;console.log(`[Process] Cache contains ${this.emailIdCache.size} processed email IDs`),console.log("[Process] First 5 cache entries:",Array.from(this.emailIdCache).slice(0,5));let h=i.filter(u=>this.emailIdCache.has(u.id)?(c++,console.log(`[Process] Skipping already processed email: ${u.id} - "${u.subject}"`),!1):(console.log(`[Process] Will process new email: ${u.id} - "${u.subject}"`),!0));e&&h.length>t&&(console.log(`[Platform] Limiting ${h.length} emails to ${t} for mobile`),h=h.slice(0,t),new y.Notice(`\u{1F4F1} Processing most recent ${t} of ${i.length} emails`,3e3)),console.log(`[Process] Processing ${h.length} new emails (${c} skipped)`);let d=3,m=Math.ceil(h.length/d);console.log(`[Process] Will process in ${m} batches of up to ${d} emails each`);for(let u=0;u<h.length;u+=d){let g=h.slice(u,u+d),f=Math.floor(u/d)+1,k=g.map(b=>b.subject||"Untitled").join(", "),p=this.settings.showDetailedNotifications?`\u{1F4DD} Processing: ${k.substring(0,50)}${k.length>50?"...":""}`:`\u{1F4DD} Processing batch ${f}/${m} (${g.length} emails)...`;this.updateStatus(p),console.log(`
[Process] === Starting Batch ${f}/${m} ===`),console.log(`[Process] Batch contains ${g.length} emails:`),g.forEach((b,T)=>{console.log(`[Process] ${T+1}. ${b.subject||"No subject"} (ID: ${b.id})`)});let w=Date.now();console.log(`[Process] Starting parallel processing at ${new Date(w).toISOString()}`);let C=g.map(async(b,T)=>{let E=Date.now();console.log(`[Process] Starting email ${T+1}/${g.length}: ${b.id}`);try{console.log(`[Process] Email ${b.id} has searchedLabels:`,b.searchedLabels);let D=j.getProcessor(b);if(!D)return console.warn(`[Process] No processor found for email ${b.id} with labels:`,b.searchedLabels),null;console.log(`[Process] Using processor: ${D.label} -> ${D.folderName}`);let L={app:this.app,claudeExtractor:this.claudeExtractor,anthropicApiKey:this.settings.anthropicApiKey,emailIdCache:this.emailIdCache,emailNotesFolder:this.settings.emailNotesFolder,saveSettings:async()=>{this.settings.processedEmails=Array.from(this.emailIdCache),await this.saveSettings()}},M=await D.process(b,L),P=Date.now()-E;if(M.success){let I=this.settings.showDetailedNotifications&&M.emailTitle?`[Process] \u2705 [${D.folderName}] "${M.emailTitle}" succeeded in ${P}ms (${M.taskCount} tasks, ${M.confidence}% confidence)`:`[Process] \u2705 [${D.folderName}] Email ${T+1} succeeded in ${P}ms (${M.taskCount} tasks, ${M.confidence}% confidence)`;return console.log(I),M}else{let I=this.settings.showDetailedNotifications&&b.subject?`[Process] \u274C "${b.subject.substring(0,50)}" failed in ${P}ms`:`[Process] \u274C Email ${T+1} failed in ${P}ms`;return console.log(I),null}}catch(D){let L=Date.now()-E;return console.error(`[Process] \u274C Email ${T+1} errored in ${L}ms:`,D),null}});console.log(`[Process] Waiting for all ${g.length} emails to complete...`);let x=await Promise.all(C),A=Date.now()-w,v=x.filter(b=>b&&b.success).length;console.log(`[Process] Batch ${f} complete: ${v}/${g.length} successful in ${A}ms`),console.log(`[Process] Average time per email: ${Math.round(A/g.length)}ms`);for(let b of x)b&&b.success&&(a++,o+=b.taskCount||0,l+=b.highPriorityCount||0,r++,b.taskCount&&b.taskCount>0&&(this.settings.showDetailedNotifications&&b.emailTitle?new y.Notice(`\u2705 ${b.emailTitle}: ${b.taskCount} tasks extracted`):new y.Notice(`\u2705 Batch ${f}: Created note with ${b.taskCount} tasks`)));v>0&&this.taskClusterer&&this.clusterNewlyCreatedTasks().catch(b=>{console.error("[Process] Background clustering failed:",b)})}if(console.log(`
[Process] === Processing Complete ===`),console.log(`[Process] Notes created: ${a}`),console.log(`[Process] Total tasks: ${o}`),console.log(`[Process] High priority tasks: ${l}`),c>0&&a===0)this.updateStatus(`\u2705 All ${c} emails already processed`),new y.Notice(`\u2705 All ${c} emails were already processed`);else if(a>0){this.updateStatus(`\u2705 Created ${a} notes (${o} tasks)`);let u=`\u2705 Successfully created ${a} meeting notes with ${o} tasks`;l>0&&(u+=` (${l} high priority)`),new y.Notice(u,5e3)}else this.updateStatus("\u2705 Processing complete"),new y.Notice("\u2705 Email processing complete (no new notes created)")}catch(e){console.error("Error processing emails:",e),this.updateStatus("\u274C Error processing emails"),new y.Notice(`\u274C Error: ${e.message}`)}}async reprocessEmailById(e,t=!0){var s;try{if(console.log(`[reprocessEmailById] Reprocessing email: ${e}`),!this.gmailService||!this.gmailService.isAuthenticated()){new y.Notice("Gmail service not authenticated");return}!this.claudeExtractor&&this.settings.anthropicApiKey&&(this.claudeExtractor=new O(this.settings.anthropicApiKey,this.settings.claudeModel),console.log("[reprocessEmailById] Initialized Claude extractor")),this.updateStatus(`\u{1F504} Fetching email ${e}...`);let i=await this.gmailService.getEmailById(e);if(!i){new y.Notice(`Email ${e} not found`);return}let a=null;if(t){let c=this.app.vault.getMarkdownFiles();for(let h of c)if(h.path.startsWith(this.settings.emailNotesFolder))try{let m=(await this.app.vault.read(h)).match(/^---\n([\s\S]*?)\n---/);if(m){let u=m[1].match(/emailId:\s*(.+)/);if(u&&u[1].trim()===e){a=h,console.log(`[Reprocess] Found existing note at: ${h.path}`);break}}}catch(d){console.error(`Error reading file ${h.path}:`,d)}}this.emailIdCache.delete(e);let o=j.getProcessor(i);if(!o){new y.Notice(`No processor found for email with labels: ${(s=i.searchedLabels)==null?void 0:s.join(", ")}`);return}a&&(console.log(`[Reprocess] Deleting old note: ${a.path}`),await this.app.vault.delete(a));let l={app:this.app,claudeExtractor:this.claudeExtractor,anthropicApiKey:this.settings.anthropicApiKey,emailIdCache:this.emailIdCache,emailNotesFolder:this.settings.emailNotesFolder,saveSettings:async()=>{this.settings.processedEmails=Array.from(this.emailIdCache),await this.saveSettings()}},r=await o.process(i,l);r.success?(this.emailIdCache.add(e),this.settings.processedEmails=Array.from(this.emailIdCache),await this.saveSettings(),new y.Notice(`\u2705 Reprocessed email with ${r.taskCount||0} tasks (Confidence: ${r.confidence}%)`),this.updateStatus(`\u2705 Reprocessed with ${r.taskCount||0} tasks`)):(new y.Notice("\u274C Failed to reprocess email"),this.updateStatus("\u274C Reprocessing failed"))}catch(i){console.error("Error reprocessing email:",i),new y.Notice(`\u274C Error: ${i.message}`),this.updateStatus("\u274C Error reprocessing")}}async saveSettings(){await this.saveData(this.settings)}updateStatus(e){this.statusBarItem&&this.statusBarItem.setText(`\u{1F4E7} ${e}`)}async openTaskDashboard(){var s;let{workspace:e}=this.app,t=e.getLeavesOfType(H);t.length>0?e.revealLeaf(t[0]):await((s=e.getRightLeaf(!1))==null?void 0:s.setViewState({type:H,active:!0}))}async reprocessCurrentMeetingNote(){try{let e=this.app.workspace.getActiveFile();if(!e){new y.Notice("No active file. Please open a meeting note to reprocess.");return}let s=(await this.app.vault.read(e)).match(/^---\n([\s\S]*?)\n---/);if(!s){new y.Notice("This file does not appear to be a meeting note (no frontmatter).");return}let a=s[1].match(/emailId:\s*(.+)/);if(!a||!a[1]){new y.Notice("No email ID found in this meeting note. Cannot reprocess.");return}let o=a[1].trim();await this.reprocessEmailById(o,!0)}catch(e){console.error("Failed to reprocess meeting note:",e),new y.Notice(`Error reprocessing: ${e.message}`),this.updateStatus("Error")}}async resetProcessedEmails(){console.log("Reset function called");try{if(this.updateStatus("Resetting..."),!confirm(`Reset Processed Emails?
This will clear all processed email records, allowing them to be processed again.`)){console.log("User cancelled reset"),this.updateStatus("Ready");return}console.log("User confirmed reset"),new y.Notice("Resetting processed emails..."),this.emailIdCache.clear(),this.settings.processedEmails=[],await this.saveSettings(),await this.loadEmailIdCache(),new y.Notice("\u2705 Cache refreshed. Existing notes will prevent duplicate processing."),this.updateStatus("Ready")}catch(e){console.error("Reset failed:",e),new y.Notice(`Reset failed: ${e.message}`),this.updateStatus("Error")}}async clusterNewlyCreatedTasks(){try{if(!this.taskClusterer){console.log("[Clustering] Task clusterer not initialized");return}console.log("[Clustering] Starting background clustering of all tasks...");let t=(await this.loadAllTasksFromVault()).filter(a=>!a.completed);if(t.length<2){console.log("[Clustering] Not enough tasks to cluster");return}let s=F.getMaxClusterTasks();F.isMobile()&&t.length>s&&(console.log(`[Platform] Limiting clustering from ${t.length} to ${s} tasks on mobile`),t=t.slice(0,s),new y.Notice(`\u{1F4F1} Clustering limited to ${s} most recent tasks on mobile`,3e3)),console.log(`[Clustering] Clustering ${t.length} tasks...`);let i=await this.taskClusterer.clusterTasks(t,this.app.vault);if(!i||i.clusters.length===0){console.log("[Clustering] No clusters found");return}await this.saveClusterIdsToTasks(i),console.log(`[Clustering] \u2705 Created ${i.clusters.length} clusters`)}catch(e){console.error("[Clustering] Failed:",e)}}async loadAllTasksFromVault(){let e=[],t=this.app.vault.getMarkdownFiles();for(let s of t)try{let a=(await this.app.vault.read(s)).split(`
`);for(let o=0;o<a.length;o++){let l=a[o],r=l.match(/^[\s-]*\[([ x])\]\s+(.+)/);if(r){let c=r[1]==="x",h=r[2],d="medium";l.includes("\u{1F534}")?d="high":l.includes("\u{1F7E2}")&&(d="low");let m=h.match(/\[\[@?([^\]]+)\]\]/),u=m?m[1]:"Unassigned",g=h.match(/📅\s*(\d{4}-\d{2}-\d{2})/),f=g?g[1]:"",k=h.match(/#(\w+)/),p=k?k[1]:"general",w=h.match(/🧩\s*cluster:([a-z0-9-]+)/),C=w?w[1]:void 0,x=h.replace(/\[\[@?[^\]]+\]\]/g,"").replace(/📅\s*\d{4}-\d{2}-\d{2}/g,"").replace(/🧩\s*cluster:[a-z0-9-]+/g,"").replace(/[🔴🟡🟢]/g,"").replace(/#\w+/g,"").trim();e.push({text:x,completed:c,assignee:u,dueDate:f,priority:d,category:p,file:s,line:o,rawLine:l,clusterId:C})}}}catch(i){continue}return e}async saveClusterIdsToTasks(e){try{for(let t of e.clusters)for(let s of t.tasks)await this.addClusterIdToTask(s,t.id);for(let t of e.standalone)t.clusterId&&await this.removeClusterIdFromTask(t)}catch(t){console.error("[Clustering] Failed to save cluster IDs:",t)}}async addClusterIdToTask(e,t){try{let i=(await this.app.vault.read(e.file)).split(`
`),a=i[e.line];a.includes("\u{1F9E9} cluster:")?a=a.replace(/🧩\s*cluster:[a-z0-9-]+/g,`\u{1F9E9} cluster:${t}`):a=a.trimEnd()+` \u{1F9E9} cluster:${t}`,i[e.line]=a,await this.app.vault.modify(e.file,i.join(`
`)),e.clusterId=t}catch(s){console.error("[Clustering] Failed to add cluster ID:",s)}}async removeClusterIdFromTask(e){try{let s=(await this.app.vault.read(e.file)).split(`
`),i=s[e.line];i=i.replace(/\s*🧩\s*cluster:[a-z0-9-]+/g,""),s[e.line]=i,await this.app.vault.modify(e.file,s.join(`
`)),e.clusterId=void 0}catch(t){console.error("[Clustering] Failed to remove cluster ID:",t)}}onunload(){console.log("Unloading Meeting Tasks Plugin...")}},se=class extends y.PluginSettingTab{constructor(n,e){super(n,e),this.plugin=e}display(){var o,l;let{containerEl:n}=this;n.empty(),n.createEl("h2",{text:"Meeting Tasks Settings"}),n.createEl("h3",{text:"Google OAuth Settings"}),n.createEl("p",{text:"Create OAuth credentials in Google Cloud Console. Follow the guide for detailed instructions.",cls:"setting-item-description"}),new y.Setting(n).setName("Google Client ID").setDesc("Your Google OAuth Client ID (from Google Cloud Console)").addText(r=>r.setPlaceholder("1234567890-abc...apps.googleusercontent.com").setValue(this.plugin.settings.googleClientId).onChange(async c=>{this.plugin.settings.googleClientId=c,await this.plugin.saveSettings(),await this.plugin.initializeServices()})),new y.Setting(n).setName("Google Client Secret").setDesc("Your Google OAuth Client Secret").addText(r=>(r.setPlaceholder("GOCSPX-...").setValue(this.plugin.settings.googleClientSecret).onChange(async c=>{this.plugin.settings.googleClientSecret=c,await this.plugin.saveSettings(),await this.plugin.initializeServices()}),r.inputEl.type="password",r)),n.createEl("h3",{text:"Gmail Authentication"});let e=n.createEl("p",{text:"\u23F3 Checking authentication status...",cls:"mod-warning setting-item-description"}),t=()=>{if(!this.plugin.gmailService){e.textContent="\u274C Gmail service not initialized",e.className="mod-warning setting-item-description";return}this.plugin.gmailService.isAuthenticated()?this.plugin.gmailService.hasRefreshToken()?(e.textContent="\u2705 Authenticated with Gmail",e.className="mod-success setting-item-description"):(e.textContent="\u26A0\uFE0F Authenticated but missing refresh token",e.className="mod-warning setting-item-description"):(e.textContent="\u274C Not authenticated with Gmail",e.className="mod-warning setting-item-description")};t(),new y.Setting(n).setName("Authenticate with Gmail").setDesc("Click to start the Gmail authentication process").addButton(r=>{var h;let c=r;(h=this.plugin.gmailService)!=null&&h.isAuthenticated()?c.setButtonText("Re-authenticate"):c.setButtonText("Authenticate"),c.onClick(async()=>{var d;if(!this.plugin.gmailService){new y.Notice("Please configure Client ID and Secret first");return}try{if(F.isMobile()){if(console.log("[Auth] Starting mobile OAuth flow"),!this.plugin.mobileOAuthHandler){new y.Notice("Mobile OAuth handler not initialized");return}new y.Notice("Opening browser for authentication...");try{let m=await this.plugin.mobileOAuthHandler.authenticate();this.plugin.settings.gmailToken={access_token:m.access_token,refresh_token:m.refresh_token,expiry_date:Date.now()+m.expires_in*1e3},await this.plugin.saveSettings(),new y.Notice("\u2705 Successfully authenticated with Gmail!"),t(),await this.plugin.initializeServices(),c.setButtonText("Re-authenticate")}catch(m){console.error("[Mobile Auth] Failed:",m),new y.Notice(`Authentication failed: ${m.message}`)}}else{if(console.log("[Auth] Starting desktop OAuth flow"),this.plugin.oauthServer||(this.plugin.oauthServer=new _),!this.plugin.oauthServer.isRunning())try{await this.plugin.oauthServer.start(),new y.Notice("Starting authentication server...")}catch(w){new y.Notice(`Failed to start OAuth server: ${w.message}`);return}let m=this.plugin.oauthServer.getRedirectUri();this.plugin.gmailService.setCredentials(this.plugin.settings.googleClientId,this.plugin.settings.googleClientSecret,m);let u=this.plugin.gmailService.getAuthorizationUrl();window.open(u,"_blank");let g=new y.Modal(this.app);g.contentEl.addClass("gmail-auth-modal"),g.contentEl.createEl("h2",{text:"\u{1F510} Authenticating with Gmail..."});let f=g.contentEl.createDiv("auth-instructions");f.createEl("p",{text:"Please complete the authorization in your browser."}),f.createEl("p",{text:"This window will close automatically when authentication is complete."});let k=g.contentEl.createDiv("auth-loading");k.style.textAlign="center",k.style.marginTop="20px",k.createEl("span",{text:"\u23F3 Waiting for authorization..."});let p=g.contentEl.createEl("button",{text:"Cancel",cls:"auth-cancel-btn"});p.style.marginTop="20px",p.onclick=async()=>{var w;g.close(),await((w=this.plugin.oauthServer)==null?void 0:w.stop())},g.open();try{let w=await this.plugin.oauthServer.waitForAuthCode();if(!w){new y.Notice("No authorization code received"),g.close(),await this.plugin.oauthServer.stop();return}g.close(),new y.Notice("Processing authentication..."),await this.plugin.gmailService.exchangeCodeForToken(w),new y.Notice("\u2705 Successfully authenticated with Gmail!"),t(),await this.plugin.initializeServices(),await this.plugin.oauthServer.stop(),c.setButtonText("Re-authenticate")}catch(w){g.close(),console.error("Authentication error:",w),new y.Notice(`Authentication failed: ${w.message}`),await((d=this.plugin.oauthServer)==null?void 0:d.stop())}}}catch(m){new y.Notice(`Failed to start authentication: ${m.message}`)}})}),new y.Setting(n).setName("Clear authentication").setDesc("Remove stored Gmail authentication").addButton(r=>r.setButtonText("Clear").setWarning().onClick(async()=>{var c;(c=this.plugin.gmailService)==null||c.clearAuthentication(),this.plugin.settings.gmailToken=null,await this.plugin.saveSettings(),new y.Notice("Gmail authentication cleared"),t()})),n.createEl("h3",{text:"Email Processing"}),new y.Setting(n).setName("Lookback time").setDesc("How far back to search. Examples: 6h (6 hours), 3d (3 days), 2w (2 weeks), 1M (1 month)").addText(r=>r.setPlaceholder("5d").setValue(this.plugin.settings.lookbackTime||"5d").onChange(async c=>{this.plugin.parseTimeToHours(c)>0&&(this.plugin.settings.lookbackTime=c,await this.plugin.saveSettings())})),new y.Setting(n).setName("Gmail Labels").setDesc("Gmail labels to filter emails (comma-separated)").addText(r=>r.setPlaceholder("transcript").setValue(this.plugin.settings.gmailLabels).onChange(async c=>{this.plugin.settings.gmailLabels=c||"transcript",await this.plugin.saveSettings()})),n.createEl("h3",{text:"Claude AI Settings"}),new y.Setting(n).setName("Anthropic API Key").setDesc("Your Claude API key for task extraction").addText(r=>r.setPlaceholder("sk-ant-...").setValue(this.plugin.settings.anthropicApiKey).onChange(async c=>{this.plugin.settings.anthropicApiKey=c,await this.plugin.saveSettings(),c&&(this.plugin.claudeExtractor=new O(c,this.plugin.settings.claudeModel))})),new y.Setting(n).setName("Claude Model").setDesc("Which Claude model to use").addDropdown(r=>r.addOption("claude-3-5-haiku-20241022","Claude 3.5 Haiku (Fast & Cheap)").addOption("claude-sonnet-4-20250514","Claude Sonnet 4 (Balanced)").addOption("claude-opus-4-1-20250805","Claude Opus 4.1 (Most Capable)").setValue(this.plugin.settings.claudeModel).onChange(async c=>{this.plugin.settings.claudeModel=c,await this.plugin.saveSettings()})),n.createEl("h3",{text:"Obsidian Settings"}),new y.Setting(n).setName("Email notes folder").setDesc("Base folder for all email-based notes (organized by label inside)").addText(r=>r.setPlaceholder("TaskAgent").setValue(this.plugin.settings.emailNotesFolder).onChange(async c=>{this.plugin.settings.emailNotesFolder=c||"TaskAgent",await this.plugin.saveSettings()})),n.createEl("h3",{text:"Dashboard Settings"}),new y.Setting(n).setName("Show only my tasks").setDesc("Filter dashboard to show only tasks assigned to you").addToggle(r=>r.setValue(this.plugin.settings.dashboardShowOnlyMyTasks).onChange(async c=>{this.plugin.settings.dashboardShowOnlyMyTasks=c,await this.plugin.saveSettings()})),new y.Setting(n).setName("My name(s)").setDesc("Your name(s) for filtering tasks (comma-separated)").addText(r=>r.setPlaceholder("Your name, other name").setValue(this.plugin.settings.dashboardMyName).onChange(async c=>{this.plugin.settings.dashboardMyName=c,await this.plugin.saveSettings()})),n.createEl("h3",{text:"Notification Settings"}),new y.Setting(n).setName("Show detailed notifications").setDesc("Show email titles in status messages while processing").addToggle(r=>r.setValue(this.plugin.settings.showDetailedNotifications).onChange(async c=>{this.plugin.settings.showDetailedNotifications=c,await this.plugin.saveSettings()})),n.createEl("h3",{text:"Actions"}),new y.Setting(n).setName("Process emails now").setDesc("Search for meeting emails and create notes").addButton(r=>r.setButtonText("Process").setCta().onClick(async()=>{await this.plugin.processEmails()})),new y.Setting(n).setName("Reset processed emails").setDesc("Clear the list of already processed emails").addButton(r=>r.setButtonText("Reset").setWarning().onClick(async()=>{await this.plugin.resetProcessedEmails()}));let s=n.createDiv("status-info"),i=(o=this.plugin.gmailService)!=null&&o.isAuthenticated()?"\u2705 Gmail authenticated":"\u274C Gmail not authenticated",a=this.plugin.settings.anthropicApiKey?"\u2705 Claude AI configured":"\u26A0\uFE0F Claude AI not configured";s.createEl("p",{text:i,cls:(l=this.plugin.gmailService)!=null&&l.isAuthenticated()?"mod-success":"mod-warning"}),s.createEl("p",{text:a,cls:this.plugin.settings.anthropicApiKey?"mod-success":"mod-warning"})}};