\")}
{"repo": "adminzachary/autotasker", "files": {"static/app.js": {"4763e96612906546e4fecdc135a1ab315f488755": {"t": 1785746684.811531, "n": 39585, "h": "005bc306f539f7b4", "c": "const defaultAiSettings = {\n provider: \"openai\",\n model: \"\",\n base_url: \"https://api.openai.com/v1\",\n api_key: \"\",\n};\n\nconst stateHelpers = window.AutoTaskerStateHelpers || {};\nconst safeJsonParse =\n stateHelpers.safeJsonParse ||\n function safeJsonParseFallback(raw, fallback) {\n try {\n return JSON.parse(raw);\n } catch {\n return fallback;\n }\n };\nconst createVersionEntry =\n stateHelpers.createVersionEntry ||\n function createVersionEntryFallback(version, assistantMessage, draft) {\n return {\n version,\n assistant_message: assistantMessage,\n task_count: draft.length,\n updated_at: new Date().toISOString(),\n };\n };\nconst readDraftWorkspace =\n stateHelpers.readDraftWorkspace ||\n function readDraftWorkspaceFallback(storage) {\n return {\n draft: safeJsonParse(storage.getItem(\"autotasker-draft\") || \"[]\", []),\n draftGoalText: storage.getItem(\"autotasker-draft-goal\") || \"\",\n draftFeedback: storage.getItem(\"autotasker-draft-feedback\") || \"\",\n draftConversation: safeJsonParse(storage.getItem(\"autotasker-draft-conversation\") || \"[]\", []),\n draftVersion: Number(storage.getItem(\"autotasker-draft-version\") || \"0\"),\n draftVersionHistory: safeJsonParse(storage.getItem(\"autotasker-draft-versions\") || \"[]\", []),\n };\n };\nconst persistDraftWorkspaceState =\n stateHelpers.persistDraftWorkspaceState ||\n function persistDraftWorkspaceStateFallback(storage, workspace) {\n storage.setItem(\"autotasker-draft\", JSON.stringify(workspace.draft || []));\n storage.setItem(\"autotasker-draft-goal\", workspace.draftGoalText || \"\");\n storage.setItem(\"autotasker-draft-feedback\", workspace.draftFeedback || \"\");\n storage.setItem(\"autotasker-draft-conversation\", JSON.stringify(workspace.draftConversation || []));\n storage.setItem(\"autotasker-draft-version\", String(workspace.draftVersion || 0));\n storage.setItem(\"autotasker-draft-versions\", JSON.stringify(workspace.draftVersionHistory || []));\n };\nconst clearDraftWorkspaceState =\n stateHelpers.clearDraftWorkspaceState ||\n function clearDraftWorkspaceStateFallback(storage) {\n storage.removeItem(\"autotasker-draft\");\n storage.removeItem(\"autotasker-draft-goal\");\n storage.removeItem(\"autotasker-draft-feedback\");\n storage.removeItem(\"autotasker-draft-conversation\");\n storage.removeItem(\"autotasker-draft-version\");\n storage.removeItem(\"autotasker-draft-versions\");\n };\nconst buildFallbackUserText =\n stateHelpers.buildFallbackUserText ||\n function buildFallbackUserTextFallback(actions, labels) {\n return (actions || []).map((item) => `[快捷操作] ${labels[item] || item}`).join(\",\");\n };\nconst applyDiscussionResult =\n stateHelpers.applyDiscussionResult ||\n function applyDiscussionResultFallback(workspace, result, options) {\n const normalizedMessage = String(options?.message || \"\").trim();\n const fallbackUserText = buildFallbackUserText(options?.actions || [], options?.quickActionLabels || {});\n const nextConversation = Array.isArray(workspace?.draftConversation) ? [...workspace.draftConversation] : [];\n\n if (normalizedMessage || fallbackUserText) {\n nextConversation.push({\n role: \"user\",\n content: normalizedMessage || fallbackUserText,\n });\n }\n\n nextConversation.push({\n role: \"assistant\",\n content: result.assistant_message,\n });\n\n const nextDraft = Array.isArray(result.updated_plan) ? [...result.updated_plan] : [];\n const nextVersion = Number(result.version || Math.max(Number(workspace?.draftVersion || 0) + 1, 1));\n const nextHistory = Array.isArray(workspace?.draftVersionHistory) ? [...workspace.draftVersionHistory] : [];\n nextHistory.push(createVersionEntry(nextVersion, result.assistant_message, nextDraft));\n\n return {\n draft: nextDraft,\n draftFeedback: result.assistant_message,\n draftConversation: nextConversation,\n draftVersion: nextVersion,\n draftVersionHistory: nextHistory,\n };\n };\n\nconst quickActionLabels = {\n split_tasks: \"拆小任务\",\n compress_schedule: \"压缩周期\",\n delay_one_day: \"推迟一天\",\n avoid_quiet_hours: \"避开安静时段\",\n raise_priority: \"提高优先级\",\n};\n\nconst initialDraftWorkspace = readDraftWorkspace(window.localStorage);\n\nconst state = {\n token: localStorage.getItem(\"autotasker-token\") || \"\",\n user: null,\n tasks: [],\n stats: null,\n userProfile: null,\n draft: initialDraftWorkspace.draft,\n draftGoalText: initialDraftWorkspace.draftGoalText,\n draftFeedback: initialDraftWorkspace.draftFeedback,\n draftConversation: initialDraftWorkspace.draftConversation,\n draftVersion: initialDraftWorkspace.draftVersion,\n draftVersionHistory: initialDraftWorkspace.draftVersionHistory,\n recentLogs: [],\n timer: safeJsonParse(localStorage.getItem(\"autotasker-timer\") || \"null\", null),\n aiSettings: { ...defaultAiSettings, ...safeJsonParse(localStorage.getItem(\"autotasker-ai-settings\") || \"{}\", {}) },\n providerPresets: {\n openai: \"https://api.openai.com/v1\",\n azure_openai: \"\",\n deepseek: \"https://api.deepseek.com/v1\",\n qwen: \"https://dashscope-intl.aliyuncs.com/compatible-mode/v1\",\n glm: \"https://open.bigmodel.cn/api/paas/v4\",\n gemini: \"https://generativelanguage.googleapis.com/v1beta\",\n },\n reviewOverride: localStorage.getItem(\"autotasker-review-override\") || \"\",\n};\n\nconst els = {\n appShell: document.querySelector(\".app-shell, .app-layout\"),\n loginStatus: document.getElementById(\"login-status-chip\"),\n navItems: Array.from(document.querySelectorAll(\".nav-item\")),\n views: Array.from(document.querySelectorAll(\".view\")),\n navFocus: document.getElementById(\"nav-focus\"),\n authPanel: document.getElementById(\"auth-panel\"),\n authForm: document.getElementById(\"auth-form\"),\n authMessage: document.getElementById(\"auth-message\"),\n username: document.getElementById(\"username\"),\n password: document.getElementById(\"password\"),\n profileCard: document.getElementById(\"profile-card\"),\n profileName: document.getElementById(\"profile-name\"),\n profileHint: document.getElementById(\"profile-hint\"),\n preferencesForm: document.getElementById(\"preferences-form\"),\n focusMinutes: document.getElementById(\"focus-minutes\"),\n breakMinutes: document.getElementById(\"break-minutes\"),\n quietHours: document.getElementById(\"quiet-hours\"),\n aiForm: document.getElementById(\"ai-form\"),\n aiProvider: document.getElementById(\"ai-provider\"),\n aiModel: document.getElementById(\"ai-model\"),\n aiBaseUrl: document.getElementById(\"ai-base-url\"),\n aiApiKey: document.getElementById(\"ai-api-key\"),\n goalForm: document.getElementById(\"goal-form\"),\n goalText: document.getElementById(\"goal-text\"),\n goalFeedback: document.getElementById(\"goal-feedback\"),\n discussionVersion: document.getElementById(\"discussion-version\"),\n discussionList: document.getElementById(\"discussion-list\"),\n discussionForm: document.getElementById(\"discussion-form\"),\n discussionInput: document.getElementById(\"discussion-input\"),\n draftList: document.getElementById(\"draft-list\"),\n versionHistory: document.getElementById(\"version-history\"),\n confirmDraft: document.getElementById(\"confirm-draft\"),\n clearDraft: document.getElementById(\"clear-draft\"),\n todoColumn: document.getElementById(\"todo-column\"),\n progressColumn: document.getElementById(\"progress-column\"),\n doneColumn: document.getElementById(\"done-column\"),\n todoCount: document.getElementById(\"todo-count\"),\n progressCount: document.getElementById(\"progress-count\"),\n doneCount: document.getElementById(\"done-count\"),\n pomodoroForm: document.getElementById(\"pomodoro-form\"),\n pomodoroTask: document.getElementById(\"pomodoro-task\"),\n pomodoroMinutes: document.getElementById(\"pomodoro-minutes\"),\n pomodoroPause: document.getElementById(\"pomodoro-pause\"),\n pomodoroDone: document.getElementById(\"pomodoro-done\"),\n pomodoroStop: document.getElementById(\"pomodoro-stop\"),\n timerCardEl: document.getElementById(\"timer-card-el\"),\n timerTaskName: document.getElementById(\"timer-task-name\"),\n timerValue: document.getElementById(\"timer-value\"),\n timerStatus: document.getElementById(\"timer-status\"),\n statsCards: document.getElementById(\"stats-cards\"),\n trendBars: document.getElementById(\"trend-bars\"),\n trendCaption: document.getElementById(\"trend-caption\"),\n reviewText: document.getElementById(\"review-text\"),\n logList: document.getElementById(\"log-list\"),\n generateReview: document.getElementById(\"generate-review\"),\n traitSummary: document.getElementById(\"trait-summary\"),\n traitPeakPeriod: document.getElementById(\"trait-peak-period\"),\n traitFocusWindow: document.getElementById(\"trait-focus-window\"),\n traitTaskStyle: document.getElementById(\"trait-task-style\"),\n traitDiscipline: document.getElementById(\"trait-discipline\"),\n periodBreakdown: document.getElementById(\"period-breakdown\"),\n quickActionButtons: Array.from(document.querySelectorAll(\"[data-quick-action]\")),\n};\n\nlet countdownTicker = null;\nconst visualState = {\n revealObserver: null,\n reduceMotion: window.matchMedia(\"(prefers-reduced-motion: reduce)\"),\n finePointer: window.matchMedia(\"(hover: hover) and (pointer: fine)\"),\n scrollTicking: false,\n};\n\nconst MOTION_CARD_SELECTOR = [\".glass\", \".task-card\", \".draft-card\", \".metric\", \".log-item\", \".trait-card\", \".period-card\"].join(\n \", \"\n);\n\nasync function api(path, options = {}) {\n const headers = { \"Content-Type\": \"application/json\", ...(options.headers || {}) };\n if (state.token) headers.Authorization = `Bearer ${state.token}`;\n const response = await fetch(path, {\n ...options,\n headers,\n body: options.body ? JSON.stringify(options.body) : undefined,\n });\n const data = await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(data.detail || data.error || \"请求失败\");\n return data;\n}\n\nfunction flash(message, isError = false) {\n if (!els.authMessage) return;\n els.authMessage.textContent = message;\n els.authMessage.style.color = isError ? \"#b5542c\" : \"#2c7a52\";\n}\n\nfunction setGoalFeedback(text, isMuted = false) {\n if (!els.goalFeedback) return;\n els.goalFeedback.textContent = text;\n els.goalFeedback.classList.toggle(\"empty\", isMuted || !text);\n}\n\nfunction persistDraftWorkspace() {\n persistDraftWorkspaceState(localStorage, state);\n}\n\nfunction clearDraftState() {\n state.draft = [];\n state.draftGoalText = \"\";\n state.draftFeedback = \"\";\n state.draftConversation = [];\n state.draftVersion = 0;\n state.draftVersionHistory = [];\n clearDraftWorkspaceState(localStorage);\n}\n\nfunction persistTimer() {\n if (state.timer) {\n localStorage.setItem(\"autotasker-timer\", JSON.stringify(state.timer));\n } else {\n localStorage.removeItem(\"autotasker-timer\");\n }\n}\n\nfunction persistAiSettings() {\n localStorage.setItem(\"autotasker-ai-settings\", JSON.stringify(state.aiSettings));\n}\n\nfunction persistReviewOverride() {\n if (state.reviewOverride) {\n localStorage.setItem(\"autotasker-review-override\", state.reviewOverride);\n } else {\n localStorage.removeItem(\"autotasker-review-override\");\n }\n}\n\nfunction formatDateTime(value) {\n if (!value) return \"未安排时间\";\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n return `${date.toLocaleDateString(\"zh-CN\", { month: \"2-digit\", day: \"2-digit\" })} ${date.toLocaleTimeString(\"zh-CN\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n })}`;\n}\n\nfunction formatShortDate(value) {\n if (!value) return \"\";\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return \"\";\n return `${date.toLocaleDateString(\"zh-CN\", { month: \"2-digit\", day: \"2-digit\" })} ${date.toLocaleTimeString(\"zh-CN\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n })}`;\n}\n\nfunction escapeHtml(value) {\n return String(value)\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n}\n\nfunction toInputDate(value) {\n if (!value) return \"\";\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return \"\";\n const offset = date.getTimezoneOffset() * 60_000;\n return new Date(date.getTime() - offset).toISOString().slice(0, 16);\n}\n\nfunction getAiConfig() {\n return {\n provider: els.aiProvider.value,\n model: els.aiModel.value.trim(),\n base_url: els.aiBaseUrl.value.trim(),\n api_key: els.aiApiKey.value.trim(),\n };\n}\n\nfunction hydrateAiForm() {\n els.aiProvider.value = state.aiSettings.provider || \"openai\";\n els.aiModel.value = state.aiSettings.model || \"\";\n els.aiBaseUrl.value = state.aiSettings.base_url || state.providerPresets[state.aiSettings.provider] || \"\";\n els.aiApiKey.value = state.aiSettings.api_key || \"\";\n}\n\nfunction syncBaseUrlPreset(force = false) {\n const provider = els.aiProvider.value;\n if (provider === \"custom_compatible\") return;\n const preset = state.providerPresets[provider] || \"\";\n if (force || !els.aiBaseUrl.value.trim()) {\n els.aiBaseUrl.value = preset;\n }\n}\n\nfunction switchView(viewName) {\n if (!els.navItems.length || !els.views.length) return;\n els.navItems.forEach((button) => button.classList.toggle(\"active\", button.dataset.view === viewName));\n els.views.forEach((view) => view.classList.toggle(\"active\", view.id === `view-${viewName}`));\n}\n\nfunction renderAuth() {\n const loggedIn = Boolean(state.user);\n els.authPanel.classList.toggle(\"hidden\", loggedIn);\n els.profileCard.classList.toggle(\"hidden\", !loggedIn);\n if (els.loginStatus) {\n els.loginStatus.textContent = loggedIn ? state.user.username : \"Offline\";\n els.loginStatus.style.background = loggedIn ? \"#e6ffe6\" : \"#ffeeee\";\n els.loginStatus.style.color = loggedIn ? \"#006600\" : \"var(--accent-red)\";\n }\n if (!loggedIn) {\n switchView(\"settings\");\n return;\n }\n els.profileName.textContent = state.user.username;\n els.profileHint.textContent = `当前默认番茄 ${state.user.focus_minutes} 分钟,休息 ${state.user.break_minutes} 分钟`;\n els.focusMinutes.value = state.user.focus_minutes;\n els.breakMinutes.value = state.user.break_minutes;\n els.quietHours.value = state.user.quiet_hours;\n els.pomodoroMinutes.value = state.user.focus_minutes;\n hydrateAiForm();\n}\n\nfunction renderDiscussion() {\n const hasDraft = state.draft.length > 0;\n els.discussionVersion.textContent = `v${state.draftVersion || 0}`;\n els.discussionInput.disabled = !hasDraft;\n els.discussionForm.querySelector(\"button\").disabled = !hasDraft;\n els.quickActionButtons.forEach((button) => {\n button.disabled = !hasDraft;\n });\n\n if (!state.draftConversation.length) {\n els.discussionList.innerHTML =\n '
生成首版草案后,你可以继续像聊天一样让 AI 重排、拆分、压缩或解释安排。
';\n return;\n }\n\n els.discussionList.innerHTML = state.draftConversation\n .map(\n (item) => `\n讨论版本历史会显示在这里。
';\n return;\n }\n\n els.versionHistory.innerHTML = [...state.draftVersionHistory]\n .reverse()\n .map(\n (item) => `\n生成草案后,可在此微调任务与时间安排。
';\n els.confirmDraft.disabled = true;\n els.clearDraft.disabled = true;\n renderVersionHistory();\n return;\n }\n\n els.confirmDraft.disabled = false;\n els.clearDraft.disabled = false;\n els.draftList.innerHTML = state.draft\n .map(\n (task, index) => `\n这里暂时没有任务。
';\n return tasks\n .map(\n (task) => `\n${escapeHtml(task.description || \"暂无说明\")}
\n完成任务后,这里会出现趋势柱状图。
';\n } else {\n const max = Math.max(...trend.map((item) => item.count), 1);\n els.trendCaption.textContent = `最近 ${trend.length} 个日期有完成记录`;\n els.trendBars.innerHTML = trend\n .map(\n (item) => `\n \n `\n )\n .join(\"\");\n }\n els.reviewText.textContent = state.reviewOverride || stats.review;\n els.reviewText.classList.toggle(\"empty\", !els.reviewText.textContent);\n}\n\nfunction renderUserProfile() {\n const profile = state.userProfile || {\n summary: \"还没有足够的历史执行数据。\",\n peak_period_label: \"待观察\",\n suggested_focus_window: \"待观察\",\n preferred_task_style_label: \"待观察\",\n scheduling_discipline_label: \"待观察\",\n period_breakdown: [],\n };\n els.traitSummary.textContent = profile.summary;\n els.traitPeakPeriod.textContent = profile.peak_period_label;\n els.traitFocusWindow.textContent = profile.suggested_focus_window;\n els.traitTaskStyle.textContent = profile.preferred_task_style_label;\n els.traitDiscipline.textContent = profile.scheduling_discipline_label;\n\n if (!profile.period_breakdown?.length || profile.total_completed_sessions === 0) {\n els.periodBreakdown.innerHTML =\n '开始记录番茄钟后,这里会识别你更擅长的时段。
';\n return;\n }\n\n els.periodBreakdown.innerHTML = profile.period_breakdown\n .map(\n (item) => `\n还没有番茄钟记录,开始第一轮后会展示在这里。
';\n return;\n }\n els.logList.innerHTML = state.recentLogs\n .map(\n (item) => `\n生成草案后,你可以在这里调整任务标题、时间和时长。
`;\n els.confirmDraft.disabled = true;\n els.clearDraft.disabled = true;\n return;\n }\n els.confirmDraft.disabled = false;\n els.clearDraft.disabled = false;\n els.draftList.innerHTML = state.draft\n .map(\n (task, index) => `\n这里暂时没有任务。
`;\n return tasks\n .map(\n (task) => `\n${escapeHtml(task.description || \"暂无说明\")}
\n完成任务后,这里会出现趋势柱状图。
`;\n } else {\n const max = Math.max(...trend.map((item) => item.count), 1);\n els.trendCaption.textContent = `最近 ${trend.length} 个日期有完成记录`;\n els.trendBars.innerHTML = trend\n .map(\n (item) => `\n \n `\n )\n .join(\"\");\n }\n els.reviewText.textContent = state.reviewOverride || stats.review;\n els.reviewText.classList.toggle(\"empty\", !els.reviewText.textContent);\n}\n\nfunction renderLogs() {\n if (!state.recentLogs.length) {\n els.logList.innerHTML = `还没有番茄钟记录,开始第一轮后会展示在这里。
`;\n return;\n }\n els.logList.innerHTML = state.recentLogs\n .map(\n (item) => `\n