Synaplan
開源 AI 平台,供企業使用:自行託管的 AI 代理、符合 GDPR 的聊天機器人小工具、RAG 知識庫、WhatsApp 與 Outlook AI、MCP、多模型路由。Docker、Kubernetes 或託管於 Germ
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證App for trainers
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Kennel calendar and trainer hub for Dogwise Academy. Phone-first, installable to the home screen.
/api/availability feeds Bark Buster and the sales assistant.With no Supabase keys the app runs on today's import of the Bookings + Availability sheet (42 trainers, 173 bookings, 42 time-off blocks). On the sign-in page pick "Admin" or any trainer. Nothing saves in demo mode.
npm install
npm run dev # http://localhost:3000
supabase/schema.sql, then supabase/seed.sql.https://<your-url>/auth/callback to redirect URLs. For real emails, add SMTP (Resend works) under Authentication → SMTP; Supabase's built-in email is rate-limited.insert into profiles (id, role, display_name)
select id, 'admin', 'Dogwise team' from auth.users where email = '[email protected]';
.env.example, deploy.sheet_color until we know what each means.lib/demo-seed.json is made-up demo data (no real people). Never put real trainer or client details in the repo.Booking dates follow the HubSpot deal (/api/hubspot/sync, every 30 minutes, and right after a booking is made).
HUBSPOT_TOKEN (deals read), HUBSPOT_SYNC = dry (default, writes nothing) → quiet (writes, no emails) → live.
Optional: HUBSPOT_START_PROP, HUBSPOT_END_PROP (found automatically as Program Start Date / Training End Date), OFFICE_EMAIL (default [email protected])./api/hubspot/sync in the browser (always a dry run from there).The client's answers to the two HubSpot forms, per dog, plus the checklist: forms, vaccines, food, medication, balance.
/a/ready. A "Not ready" incident opens 7 days before a dog starts if anything is missing, and clears itself.k9___dog_name). Dogs 2 to 4 (dog_2_name…) are kept in the app, so they never overwrite dog 1.vaccines_in_order_ and missing_vaccines on the deal. Clients upload records to the chat's Drive folder.forms on the deal once every answer is in, and fills pre_training_missing if you create that deal property (single-line text).crm.objects.deals.read/write, crm.objects.contacts.read/write, crm.schemas.deals.read, crm.schemas.contacts.read.Only the must haves hold up a dog's start: To do, "Not ready", the client's reminders and pre_training_missing. The list is MUST_KEYS in lib/pretrainingFields.ts: the client's details, address and household, vet and emergency contact, CPR consent, Terms & Conditions, medical conditions, illness in the last 7 days, food, medication, training goals, bite history and aggression towards other dogs. The other questions are still asked on the client's form and show as "optional"; they don't hold anything up.
A dog name on the deal that looks like two dogs ("Francesca & Jack") or like a person ("William Smith" for a Smith) shows on To do with Fix in HubSpot and Keep dog name. Fixing it in HubSpot clears it within 30 minutes; a kept name is never flagged again.
FORMS_CUTOVER in lib/reports.ts. Intake forms and weekly checks due before it were done the old way: the app doesn't ask for them (not late, no penalty). Handover reports due before it are still needed for the client's certificate: they show as "Needed for the certificate" (never late), grouped on To do. Press "Not needed" on any that already went out the old way.
/a/sales, and /s for a sales login on SALES_MANAGER_EMAILS (who then sees the whole team as admin does, and nothing else in admin). Above the month's sales by person: the two Chrome extensions (Bark Buster for admin and managers, Sales Assistant for every sales login) with a "How to load unpacked" fold-out; then the comparisons (lib/salesInsights.ts, one 25-month pull of Closed Won deals from HubSpot plus a few counts, cached per month for an hour, dropped by Refresh from HubSpot): this month against the same month last year to the same day and in full, against last month to the same day, what last year says about next month, a 13-month strip with last year's months behind, each salesperson (month, last month, same month last year, average deal, days to close, discount, calls and calls per sale), leads and conversion vs last year, days to close, discounting, what sells by program vs last year, lost reasons, payment plans not approved, failed last card attempts (Stripe) and refunds. Both pages show a loading card while the month loads.
/a/pulse, for admins on PULSE_EMAILS (default [email protected]); others get a 404. One snapshot is built (buildSnapshot): the last 90 days plus the 90 before, from the app's tables (every read paged past Supabase's 1,000-row cap), HubSpot (deals created, won, lost, same dates last year, this and next month last year, per-salesperson counts of leads, calls and emails per preset) and Stripe (charges, so "whose last card attempt failed"), squeezed into a compact shape and cached for an hour (snapshotCached, tag pulse; Refresh rebuilds it). Every range (presets or custom, within 90 days) and view is then worked out from it instantly. Views, switched like the Chats page: Office (clients and the link to trainers: Pedro's weekly list, pickups and drop-offs in the range, To do counts), Trainers (every active trainer; a score out of 100 = daily updates 40, replies 25, forms 20, alerts 15, parts without data dropped, small samples pulled toward the group; "Low data" and "Unusual for this trainer" flags against the 90-day baseline; a plain-English summary; kennels used, days off) and Sales (leads and sold against the same dates last year, conversion vs last year, cost per lead and per sale counted only on deals with Bark credits, "what to beat" this month vs last year, next month last year, a US tile map where the top number is leads, the square fills with sales and a red edge means that state's kennels are 80%+ full, a sales-per-day/week chart against last year filtered by the tapped state, sources with conversion and cost per sale, salespeople with calls and emails). Today's pickups and drop-offs also sit at the top of To do (lib/moves.ts).
Asks why (lib/cancelReasons.ts, saved on the booking: migration 040) and says plainly who's told: the trainer gets a cancellation with a calendar removal, the office gets a "Training cancelled" alert with the reason, and the kennel frees up. Only for trainings that were really cancelled. The reason isn't in the trainer's email.
Groups stay as they are; inside each group the newest message is first. "By dates" (?o=dates) puts the soonest drop-off / start first instead. The trainer's list is still by dates.
Migration 041. The clock next to Send opens a sheet: write the message, add photos or videos, pick when (quick picks or any date and time, up to 30 days ahead). It's kept in scheduled_messages and posted into the chat by the 10-minute chat job (lib/scheduled.ts) at that time, as the person who scheduled it, so the client sees nothing before (no message, preview or notification). Pending ones show in a "Scheduled" strip above the composer with Cancel (yours, or any as admin). If the chat closes first, it's cancelled with a note.
Migration 039. On a chat page (or a To do row for "hasn't posted" / "client waiting"), Quiet chat stops that chat's "hasn't posted today", "client waiting" and "unanswered messages" alerts (and clears open ones), its client "new updates" emails and its automatic invite. Messages still notify as normal if anyone posts. Only the emails in QUIET_CHAT_EMAILS (default [email protected]) see the button, and the server checks it; everyone else sees "Quiet: finishing on Signal". Alerts back on undoes it.
Your account → Appearance (admin and sales), or the trainer's Profile page: Light (default) or Dark. Saved on that device (localStorage dw-theme) and set before the page paints, so it doesn't flash light first.
app/globals.css (light under :root, dark under [data-theme="dark"]); tailwind.config.ts explains the rules. Every text/background pair in the app passes 4.5:1 in both themes.bg-[#fff] text-[#281870], as the payslip and report previews do.public/brand-logo-dark.png, via components/BrandLogo.tsx). Free kennels show as outlined boxes in dark mode, booked ones filled.Each payslip has a Mileage check: every pickup, drop-off and follow-up trip in the period, the miles the trainer declared, the driving route from the trainer to the client (doubled for the round trip), and a Check on Google Maps link for the same trip.
FREEROUTE_API_KEY in Vercel; free tier 1,000 requests a day, we use a few). Its address search can pick a same-name town in another state, so an address only counts when the result is in the same ZIP (or state); otherwise the ZIP's centre is used. If FreeRoute errors or doesn't answer, it's a straight-line estimate + 25% for roads, marked "estimate", tried again 6 hours later. No key = estimates only. FreeRoute doesn't report tolls; Google does, on the link.trip_routes (migration 038), redone only when an address changes. The 20-minute forms job checks a few at a time; Check now on the payslip does that trainer's trips straight away.supabase/migrations/032_audit_outcomes_and_integrity.sql, then 033_server_notifications_and_direct_edit_log.sql (in that order; don't re-run 032 after 033).CHAT_AUTO_INVITE (on). Clients get their chat link 7 days before training, or straight away if the booking is later than that. off stops it.UNBOOKED_FROM = 2026-10-11. Closed Won deals from this date with no booking show on To do.What changed:
/api/chat/notify the moment a message is saved (pg_net), the sender's app asks too, and the 10-minute check sends anything both missed (logged as "Sent missed chat notifications"). The app registers its own address on its first chat check after deploying; nothing to set by hand.LOG_VIEWERS (alma@) within 10 minutes. Nothing is blocked.app.dogwiseacademy.com, then add the DNS record Vercel shows at your DNS host.NEXT_PUBLIC_SITE_URL = https://app.dogwiseacademy.com, APP_HOST = app.dogwiseacademy.com. Redeploy.https://app.dogwiseacademy.com, and add https://app.dogwiseacademy.com/** to Redirect URLs (keep the vercel.app ones).
Page visits to the vercel.app address then move to the new one. /api keeps working on both, so Stripe, the HubSpot extension, crons and the availability feed need no change.
Everyone signs in once more on the new address (password or "Email me a code") and re-adds the app to their home screen; turning notifications on there stops them on the old install.After 53 days with no messages from anyone (and no dog in care now or coming up), a notice goes in the chat: "This chat will close on unless someone writes here." Seven days later, if still quiet, the chat closes: the transcript is saved to Drive and emailed to the client, as when an admin closes it. Each step is in the activity log. Anyone writing in the chat restarts the clock. CHAT_AUTO_CLOSE=off in Vercel goes back to closing by hand ("Ready to close" on To do).
The app is a custom connector in Claude, so whoever adds it can ask Claude about bookings, chats and forms.
https://<app address>/api/mcp. In the connector's settings choose "No sign-in" and add the request header Authorization with the value Bearer <CLAUDE_MCP_KEY>. (Request headers are still a beta in Claude: if the dialog has no "Request headers" section, the account doesn't have it yet.)todo (the admin To do list), dogs_in_care, forms, before_training, find_dog, chat_messages, availability, and for communication: chats (every open chat and team thread, with who is waiting on whom and for how long), recent_messages (what was said since a given time, across all chats and team threads) and trainer_comms (each trainer's daily updates and reply times over the last days). They live in lib/claudeConnector.ts and lib/claudeComms.ts; the MCP part is app/api/mcp/route.ts.todo shares the To do page's code, whose only write is its one-time "watching background jobs since" date, already set in production.)CLAUDE_MCP_KEY is set (32 characters or more). Make one with openssl rand -hex 32. To change it: set the new key in Vercel and redeploy, then remove the connector in Claude and add it again with the new header (Claude can't edit a saved header).