From 7cf6e8b8a18fa9e09977ede5171324b3fa81d929 Mon Sep 17 00:00:00 2001 From: Kritika-1301 Date: Fri, 24 Jul 2026 13:59:13 +0530 Subject: [PATCH] feat: add AI chatbot integration and update UI components --- README.md | 67 ++ apps/backend/package.json | 6 +- .../src/__tests__/discordIngestion.test.ts | 245 ++++ apps/backend/src/bootstrap/startup.ts | 36 + apps/backend/src/faqs.json | 1014 ++++++++++++++++- .../src/integrations/discord/botManager.ts | 13 +- .../discord/discordIngestionService.ts | 140 +++ apps/backend/src/middleware/programScope.ts | 8 +- .../modules/admin/admin-welcome.controller.ts | 3 +- .../program/feature-flag.controller.ts | 2 +- .../program/registration-config.model.ts | 12 +- apps/backend/src/scripts/seed.ts | 3 + apps/backend/src/scripts/seedPosts.ts | 13 +- apps/backend/src/server.ts | 4 + apps/backend/src/utils/ai/embeddings.ts | 7 +- apps/backend/src/utils/documentExtractor.ts | 18 +- apps/backend/src/vitest.setup.ts | 5 + apps/backend/vitest.config.ts | 1 + .../src/admin/pages/AdminAISettings.tsx | 1 + .../admin/pages/AdminProgramSettingsPage.tsx | 2 +- .../src/components/layout/NavPills.tsx | 18 +- .../frontend/src/components/layout/Navbar.tsx | 45 +- .../src/context/FeatureFlagContext.tsx | 19 +- apps/frontend/src/context/ProgramContext.tsx | 10 +- apps/frontend/src/pages/HomePage.tsx | 9 +- apps/frontend/src/pages/ProgramPage.tsx | 4 +- apps/frontend/src/pages/ProgramPortalPage.tsx | 257 ++++- .../src/pages/Yaksha2026_27ProgramPage.tsx | 2 +- .../src/pages/deprecated/HomePage.tsx | 499 -------- apps/frontend/vite.config.ts | 6 +- ask.json | 1 + package.json | 5 +- pnpm-lock.yaml | 119 +- run.bat | 6 + run.sh | 4 +- 35 files changed, 1982 insertions(+), 622 deletions(-) create mode 100644 apps/backend/src/__tests__/discordIngestion.test.ts create mode 100644 apps/backend/src/integrations/discord/discordIngestionService.ts create mode 100644 apps/backend/src/vitest.setup.ts delete mode 100644 apps/frontend/src/pages/deprecated/HomePage.tsx create mode 100644 ask.json create mode 100644 run.bat diff --git a/README.md b/README.md index 79dc06b0..7df50d36 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,55 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the architecture deep-dive ## Quick Start +### πŸš€ One-Command Local Development (In-Memory MongoDB) + +Run the local dev stack with zero setup required (starts an in-memory MongoDB, seeds 165 FAQs + 5 Community Posts, launches backend on port 6767, and frontend on port 5173): + +```bash +node start-local.mjs +``` + +Or from the `crowd-source-faq` directory in PowerShell: +```powershell +cd crowd-source-faq +node start-local.mjs +``` + +--- + +### πŸ’» Terminal Access & CLI Commands + +You can interact with and query the live portal directly through your terminal: + +#### πŸ€– Ask the AI Chatbot: +```powershell +Invoke-RestMethod -Uri 'http://localhost:5173/csfaq/api/ask-ai' -Method Post -Body '{"question":"What are Spurti points?"}' -ContentType 'application/json' | Select-Object -ExpandProperty answer +``` + +#### πŸ“š Browse FAQs: +```powershell +(Invoke-RestMethod -Uri 'http://localhost:5173/csfaq/api/faq?limit=5').faqs | Select-Object question, category +``` + +#### πŸ’¬ Browse Community Threads: +```powershell +(Invoke-RestMethod -Uri 'http://localhost:5173/csfaq/api/community/').posts | Select-Object title, status +``` + +#### πŸ—‚οΈ Check Active Programs: +```powershell +(Invoke-RestMethod -Uri 'http://localhost:5173/csfaq/api/batches').batches | Select-Object name, isActive, isDefault, faqCount +``` + +#### πŸ₯ System Health Audit: +```powershell +Invoke-RestMethod -Uri 'http://localhost:5173/csfaq/api/health' +``` + +--- + +### πŸ› οΈ Production / Full-Stack Runner (`run.sh`) + ```bash ./run.sh # Full-stack runner: env setup, ngrok, backend + frontend ``` @@ -55,6 +104,24 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the architecture deep-dive --- +## Push to GitHub + +To push this project to your own GitHub repository, follow these steps: + +1. Create a new empty repository on GitHub. +2. Open your terminal in the project root (`crowd-source-faq`). +3. Run the following commands: + ```bash + git init + git add . + git commit -m "Initial commit" + git branch -M main + git remote add origin + git push -u origin main + ``` + +--- + ## Key Features Eight flagship capabilities define this platform: diff --git a/apps/backend/package.json b/apps/backend/package.json index 62c82b3a..63d602c2 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -36,11 +36,11 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@google-cloud/storage": "^7.21.0", - "@sentry/node": "^10.55.0", "@csfaq/types": "workspace:*", "@csfaq/utils": "workspace:*", "@csfaq/validation": "workspace:*", + "@google-cloud/storage": "^7.21.0", + "@sentry/node": "^10.55.0", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "discord-api-types": "^0.38.48", @@ -73,7 +73,9 @@ "@types/node": "^25.9.1", "@types/range-parser": "^1.2.7", "@types/uuid": "^10.0.0", + "canvas": "^3.2.3", "nodemon": "^3.0.2", + "playwright": "^1.61.1", "tsx": "^4.22.3", "typescript": "^6.0.3", "vitest": "^1.6.0" diff --git a/apps/backend/src/__tests__/discordIngestion.test.ts b/apps/backend/src/__tests__/discordIngestion.test.ts new file mode 100644 index 00000000..7f2d584b --- /dev/null +++ b/apps/backend/src/__tests__/discordIngestion.test.ts @@ -0,0 +1,245 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { handleMessageReaction } from '../integrations/discord/discordIngestionService.js'; +import { Types } from 'mongoose'; + +// Hoisted mocks for dependencies +const mockFaqCreate = vi.fn(); +const mockChatWithConfig = vi.fn(); +const mockGetPipelineProviderConfig = vi.fn(); + +vi.mock('../modules/faq/faq.model.js', () => ({ + default: { + create: (data: any) => mockFaqCreate(data), + }, +})); + +vi.mock('../utils/ai/aiProvider.js', () => ({ + chatWithConfig: (cfg: any, msgs: any[]) => mockChatWithConfig(cfg, msgs), + getPipelineProviderConfig: (name: string) => mockGetPipelineProviderConfig(name), +})); + +// Mock logger to avoid spamming test output +vi.mock('../utils/http/logger.js', () => ({ + logger: { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + }, +})); + +describe('discordIngestionService: handleMessageReaction', () => { + const dummyConfig = { + batchId: new Types.ObjectId().toString(), + guildId: '123456789', + botToken: 'mock-token', + applicationId: 'mock-app-id', + clientId: 'mock-client-id', + webhookUrl: null, + notificationChannelId: null, + adminUserIds: [], + publicChannelId: null, + publicUrl: 'http://localhost:6767', + internalApiKey: null, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('ignores bots reacting', async () => { + const mockReaction = { + emoji: { name: 'βœ…' }, + partial: false, + message: { + author: { bot: false }, + content: 'answer content', + react: vi.fn(), + }, + }; + const mockUser = { bot: true, tag: 'bot#1234' }; + + await handleMessageReaction(mockReaction as any, mockUser as any, dummyConfig); + + expect(mockFaqCreate).not.toHaveBeenCalled(); + }); + + it('ignores non-trigger emojis', async () => { + const mockReaction = { + emoji: { name: 'πŸ‘Ž' }, + partial: false, + message: { + author: { bot: false }, + content: 'answer content', + react: vi.fn(), + }, + }; + const mockUser = { bot: false, tag: 'user#1234' }; + + await handleMessageReaction(mockReaction as any, mockUser as any, dummyConfig); + + expect(mockFaqCreate).not.toHaveBeenCalled(); + }); + + it('ignores bot-authored messages', async () => { + const mockReaction = { + emoji: { name: 'βœ…' }, + partial: false, + message: { + author: { bot: true }, + content: 'bot answer content', + react: vi.fn(), + }, + }; + const mockUser = { bot: false, tag: 'user#1234' }; + + await handleMessageReaction(mockReaction as any, mockUser as any, dummyConfig); + + expect(mockFaqCreate).not.toHaveBeenCalled(); + }); + + it('extracts Q&A and creates a pending FAQ from thread context', async () => { + const mockReact = vi.fn(); + const fetchStarterMessageMock = vi.fn().mockResolvedValue({ content: 'What is the program deadline?' }); + const mockReaction = { + emoji: { name: 'βœ…' }, + partial: false, + message: { + id: 'msg_98765', + author: { bot: false }, + content: 'The deadline is next Monday.', + channel: { + isThread: () => true, + fetchStarterMessage: fetchStarterMessageMock, + }, + react: mockReact, + }, + }; + const mockUser = { bot: false, tag: 'reviewer#5678' }; + + mockGetPipelineProviderConfig.mockResolvedValue({ provider: 'openai', model: 'gpt-4' }); + mockChatWithConfig.mockResolvedValue('{"question": "What is the program deadline?", "answer": "The deadline is next Monday."}'); + mockFaqCreate.mockResolvedValue({ _id: 'faq_123' }); + + await handleMessageReaction(mockReaction as any, mockUser as any, dummyConfig); + + expect(fetchStarterMessageMock).toHaveBeenCalled(); + expect(mockGetPipelineProviderConfig).toHaveBeenCalledWith('auto_answer'); + expect(mockChatWithConfig).toHaveBeenCalled(); + expect(mockFaqCreate).toHaveBeenCalledWith( + expect.objectContaining({ + question: 'What is the program deadline?', + answer: 'The deadline is next Monday.', + status: 'pending', + reviewStatus: 'pending_review', + batchId: expect.any(Types.ObjectId), + }) + ); + expect(mockReact).toHaveBeenCalledWith('πŸ’Ύ'); + }); + + it('extracts Q&A and creates pending FAQ when not a thread', async () => { + const mockReact = vi.fn(); + const mockReaction = { + emoji: { name: 'πŸ’‘' }, + partial: false, + message: { + id: 'msg_98766', + author: { bot: false }, + content: 'How do I contact support? Email support@example.com', + channel: { + isThread: () => false, + }, + react: mockReact, + }, + }; + const mockUser = { bot: false, tag: 'reviewer#5678' }; + + mockGetPipelineProviderConfig.mockResolvedValue({ provider: 'openai', model: 'gpt-4' }); + mockChatWithConfig.mockResolvedValue('{"question": "How do I contact support?", "answer": "Email support@example.com"}'); + mockFaqCreate.mockResolvedValue({ _id: 'faq_124' }); + + await handleMessageReaction(mockReaction as any, mockUser as any, dummyConfig); + + expect(mockGetPipelineProviderConfig).toHaveBeenCalledWith('auto_answer'); + expect(mockChatWithConfig).toHaveBeenCalled(); + expect(mockFaqCreate).toHaveBeenCalledWith( + expect.objectContaining({ + question: 'How do I contact support?', + answer: 'Email support@example.com', + status: 'pending', + reviewStatus: 'pending_review', + }) + ); + expect(mockReact).toHaveBeenCalledWith('πŸ’Ύ'); + }); + + it('extracts Q&A and fetches referenced message content when reacting to a reply', async () => { + const mockReact = vi.fn(); + const fetchMessageMock = vi.fn().mockResolvedValue({ content: 'How do I generate API keys?' }); + const mockReaction = { + emoji: { name: 'βœ…' }, + partial: false, + message: { + id: 'msg_98768', + author: { bot: false }, + content: 'Go to settings -> API and click generate.', + reference: { + messageId: 'msg_referenced_123', + }, + channel: { + isThread: () => false, + messages: { + fetch: fetchMessageMock, + }, + }, + react: mockReact, + }, + }; + const mockUser = { bot: false, tag: 'reviewer#5678' }; + + mockGetPipelineProviderConfig.mockResolvedValue({ provider: 'openai', model: 'gpt-4' }); + mockChatWithConfig.mockResolvedValue('{"question": "How do I generate API keys?", "answer": "Go to settings -> API and click generate."}'); + mockFaqCreate.mockResolvedValue({ _id: 'faq_125' }); + + await handleMessageReaction(mockReaction as any, mockUser as any, dummyConfig); + + expect(fetchMessageMock).toHaveBeenCalledWith('msg_referenced_123'); + expect(mockGetPipelineProviderConfig).toHaveBeenCalledWith('auto_answer'); + expect(mockChatWithConfig).toHaveBeenCalled(); + expect(mockFaqCreate).toHaveBeenCalledWith( + expect.objectContaining({ + question: 'How do I generate API keys?', + answer: 'Go to settings -> API and click generate.', + status: 'pending', + reviewStatus: 'pending_review', + }) + ); + expect(mockReact).toHaveBeenCalledWith('πŸ’Ύ'); + }); + + it('gracefully handles invalid JSON from AI', async () => { + const mockReact = vi.fn(); + const mockReaction = { + emoji: { name: 'βœ…' }, + partial: false, + message: { + id: 'msg_98767', + author: { bot: false }, + content: 'Just check the website.', + channel: { + isThread: () => false, + }, + react: mockReact, + }, + }; + const mockUser = { bot: false, tag: 'reviewer#5678' }; + + mockGetPipelineProviderConfig.mockResolvedValue({ provider: 'openai', model: 'gpt-4' }); + mockChatWithConfig.mockResolvedValue('This is not JSON text.'); + + await handleMessageReaction(mockReaction as any, mockUser as any, dummyConfig); + + expect(mockFaqCreate).not.toHaveBeenCalled(); + expect(mockReact).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/backend/src/bootstrap/startup.ts b/apps/backend/src/bootstrap/startup.ts index f6092746..6a5141b8 100644 --- a/apps/backend/src/bootstrap/startup.ts +++ b/apps/backend/src/bootstrap/startup.ts @@ -42,6 +42,42 @@ export async function startup(config: any): Promise { startupLog.error('startup DB connect / migrate failed', { error: (e as Error).message }); } + // ── Auto-seed a default program on first boot ────────────────────────────── + // On a fresh in-memory or empty MongoDB, there are no programs so the + // portal shows "No programs yet." This seeds one so the app is immediately + // usable. It is idempotent β€” runs only if no programs exist at all. + try { + const { default: Batch } = await import('../modules/program/batch.model.js'); + const { bootstrapProgram } = await import('../modules/program/provisioning.service.js'); + const existingCount = await Batch.countDocuments(); + if (existingCount === 0) { + const now = new Date(); + const start = new Date(now.getFullYear(), now.getMonth(), 1); // First of this month + const end = new Date(now.getFullYear(), now.getMonth() + 6, 0); // 6 months later + const seeded = await Batch.create({ + name: 'Default Program', + description: 'Auto-created default program. Rename or replace it from Admin β†’ Programs.', + startDate: start, + endDate: end, + isActive: true, + isDefault: true, + status: 'active', + enrollmentMode: 'open', + maxEnrollment: null, + createdBy: null, + ownerUserId: null, + }); + try { + await bootstrapProgram(seeded._id); + } catch (_bootstrapErr) { + // Non-fatal β€” categories/settings can be set up manually + } + startupLog.info(`[seed] Created default program "${seeded.name}" (${seeded._id})`); + } + } catch (e) { + startupLog.warn(`[seed] Default program seed failed (non-fatal): ${(e as Error).message}`); + } + // Lazy-init the RegistrationConfig singleton try { const { ensureRegistrationConfig } = await import('../modules/program/registration-config.model.js'); diff --git a/apps/backend/src/faqs.json b/apps/backend/src/faqs.json index 3d581f70..c1bd720c 100644 --- a/apps/backend/src/faqs.json +++ b/apps/backend/src/faqs.json @@ -1,11 +1,1015 @@ { + "source": "https://samagama.in/internship/faq", + "version": "v24.8.0", + "last_updated": "2026-07-18, IST", + "total_faqs": 165, + "sections": [ + "1. About the internship", + "2. Timing and dates", + "3. NOC (No Objection Certificate)", + "4. Selection, offer letter, and certificate", + "5. Work and projects", + "6. Code of conduct β€” communication channels", + "7. Interviews Related", + "8. Certificate", + "9. Rosetta β€” your internship journal", + "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "11. Spurti Points", + "12. Yaksha Chat Related", + "13. ViBe Platform", + "14. Team Formation", + "15. Spurti Levels, Trophy Leagues & Feedback Survey" + ], "faqs": [ { - "id": "1", - "section": "General", - "question": "What is the Yaksha research internship?", - "answer": "Yaksha is a two-month research internship program.", - "category": "General Info" + "id": "1.1", + "section": "1. About the internship", + "question": "What is the Vicharanashala internship?", + "answer": "A two-month engagement at the Vicharanashala Lab, a research lab at IIT Ropar. You will work on a real open-source project under a mentor, after a short training phase tailored to where you already are. The internship is free β€” we do not charge, and the work is real. It runs on one of two tracks, decided by the start date you confirm: a full-time, live-session track if your confirmed start date is on or before 15 July 2026, or an asynchronous self-paced / parallel track if it's after that (see Β§2.7). As of today, most new candidates land on the self-paced / parallel track." + }, + { + "id": "1.2", + "section": "1. About the internship", + "question": "What is VINS?", + "answer": "VINS is the Vicharanashala Internship β€” an online programme open to anyone who clears our interview. The work is real open-source contribution under a mentor, the certificate is from the Vicharanashala Lab for Education Design at IIT Ropar, and the programme itself is free (we charge nothing). There is no stipend. If you are seeing a yellow VINS panel on your result page, you are selected." + }, + { + "id": "1.3", + "section": "1. About the internship", + "question": "What are the phases of VINS, and what do the badges mean?", + "answer": "VINS is structured as four phases. Each one is marked by a badge β€” a small token of where you are in the journey. πŸ₯‰ Bronze (Phase 1) β€” a short training period at the start, planned around what you already know. If you arrive already comfortable with the basics, you may skip Bronze and go straight on to the project. Bronze culminates in a CSFAQ project submission β€” you submit a GitHub link (repo or PR) along with product documentation and a project report. See Β§10.15 for details. πŸ₯ˆ Silver (Phase 2) β€” the main work. You contribute to a real open-source project under a Vicharanashala mentor. Finishing Bronze and Silver completes your internship and earns the certificate. πŸ₯‡ Gold (Phase 3) β€” a recognition awarded during Silver if your contribution stands on its own as a meaningful feature, not just a small fix. πŸ† Platinum (Phase 4) β€” a standing invitation to come back and visit the lab β€” a short trip β€” any time during the year after your internship ends. We help with travel through a small visit stipend. Most interns finish at Bronze + Silver, and that is exactly what the certificate is for. Gold and Platinum are extras you can pick up if your work makes the case for them. Either way, you walk away with a real open-source contribution to your name and a mentor who knows you well." + }, + { + "id": "1.4", + "section": "1. About the internship", + "question": "Who is the internship for? Are alumni eligible?", + "answer": "The internship is for currently-enrolled students at any college or university β€” undergraduate, postgraduate, or doctoral. The NOC requirement is the practical reflection of this: we ask for institutional consent that you can commit your time to this internship. Candidates who have already graduated and are not currently enrolled in any programme are not eligible for this cycle. If you re-enrol later (higher studies, etc.), you are very welcome to apply again in a future cycle." + }, + { + "id": "1.5", + "section": "1. About the internship", + "question": "Is this the same as IIT Ropar's official Summer Research Internship?", + "answer": "No. Summership 2026 is a VLED Lab initiative. The certificate is issued by the Vicharanashala Lab for Education Design, not centrally by the institute. IIT Ropar runs a separate institutional summer research internship through its own office. Do not represent Summership 2026 as equivalent to that programme." + }, + { + "id": "1.6", + "section": "1. About the internship", + "question": "I have to attend my class tomorrow/today/some day β€” can I take leave?", + "answer": "Leave is not permitted. If you are also attending classes or exams, you will be relieved from the internship immediately and will need to join the next batch when it starts." + }, + { + "id": "2.1", + "section": "2. Timing and dates", + "question": "When can I start?", + "answer": "You can start any time in 2026 β€” VINS is flexible on the start date. Which track you land on depends entirely on the date you confirm: Confirm a start date on or before 15 July 2026 β†’ you're on the live-session track (mandatory daily Zoom, week-by-week structure β€” see Β§2.7 for the comparison). Confirm a start date after 15 July 2026 β†’ you're on the **self-paced / parallel track** instead (four verticals, asynchronous, no mandatory live sessions). As of today, this is where most new candidates land β€” it is not a rare case. Whichever track, one hard rule applies to both: your internship must finish by 31 December 2026. Whatever start you pick, your end date (your start + 2 months, with up to 1 month grace) must land on or before 31 December 2026. So while there's no last date to opt in, there is absolutely a last date to finish. If you can still make it onto the live-session track, start as soon as possible. The earlier you join, the more of the May–July main cohort you catch β€” three things make starting earlier materially better, within that track: Cohort networking. The batch goes through Bronze together β€” peer discussions, parallel problem-solving, and lasting connections happen during this window. Later in the year the cohort disperses and late starters are largely solo. TA support is concentrated in May–July. TAs are full-time during this window. After this they return to their own college work and bandwidth is materially thinner. Training rolls out with the cohort, not piecemeal β€” you get the material with the discussion around it, not as a static document. This only matters if a live-session-track start is still realistic for you. If your confirmed date will land after 15 July 2026, you're on the self-paced / parallel track regardless of any of the above β€” see Β§2.7 for what that actually looks like, rather than reading the rest of this entry as \"the same thing, just with weaker support.\"" + }, + { + "id": "2.2", + "section": "2. Timing and dates", + "question": "How long is the internship?", + "answer": "Two months from your chosen start date, with an optional one-month grace period if you need it. End must land on or before 31 December 2026." + }, + { + "id": "2.3", + "section": "2. Timing and dates", + "question": "Can I start in July, August or later if I have exams now?", + "answer": "Yes β€” but only if your exams genuinely make an earlier start impossible. Wait until your exams are done, then opt in and start. Do not attempt to juggle this internship with ongoing exams. Make sure your chosen start date plus 2 months (or 3 with grace) lands on or before 31 December 2026." + }, + { + "id": "2.4", + "section": "2. Timing and dates", + "question": "Can I start with the cohort and take a relaxation during my exam window?", + "answer": "No. This is not an arrangement we offer. VINS is a full-attention internship β€” six to ten hours a day, sometimes more. Splitting that with college exams damages both sides: the project loses momentum, the exams suffer, and the lab invests in someone who can only half-engage. We have seen this fail enough times to be firm. If your exams fall inside the cohort duration, defer your start to after your exams end, opt in then, and run the internship at full attention. The certificate and project pathway are the same. This answer is about the standard, live-session track. If your confirmed start date ends up after 15 July 2026, you'd be on the self-paced / parallel track instead (see Β§2.7), where this full-attention/hours expectation doesn't apply β€” though deferring past exams is still the right call either way. A note on consequences. If we later learn that a candidate was sitting college exams during their internship period, we reserve the right to terminate the internship or withhold the certificate at any time β€” including after the internship has otherwise been completed." + }, + { + "id": "2.5", + "section": "2. Timing and dates", + "question": "Can I take leave or get an exemption during the internship for an exam scheduled in June?", + "answer": "The attendance rule is firm β€” the 55-day continuous window is a non-negotiable part of the internship, and we cannot offer an exemption for an exam during this period. The policy exists because split attention genuinely damages both your exam preparation and your internship work." + }, + { + "id": "2.6", + "section": "2. Timing and dates", + "question": "Are orientation session recordings shared with interns, and can project or group assignments be changed after watching them?", + "answer": "Recordings of the sessions will not be provided. However, we may provide access to an abridged version of a talk or session if we consider it important. We do not guarantee this for every session." + }, + { + "id": "2.7", + "section": "2. Timing and dates", + "question": "Is there a self-paced / parallel track? What if my confirmed start date is after 15 July 2026?", + "answer": "Yes. If you confirm a start date after 15 July 2026, you are automatically placed on the self-paced / parallel internship track instead of the standard full-time, live-session journey described elsewhere in this FAQ. On this track, your dashboard shows 4 verticals instead of the usual step-by-step timeline: Foundations of Data Science (Matrix Mystics) β€” the only vertical live today; work through the question set and get endorsed. Projects β€” criteria will be announced soon.ViBe β€” criteria will be announced soon.Standup β€” complete a total of 3,600 minutes of standup participation, at your own pace. You are not required to attend live Zoom sessions on this track β€” the mandatory-attendance, 85% participation, and quiz-pass rules described elsewhere (Β§5.2, Β§10.4, Β§10.6, Β§10.10) apply to the standard track, not to this one. This does not mean there's no schedule at all: weekly milestones are mandatory and are emailed to you separately β€” the flexibility is in when within the week you work, not whether you meet them. You'll also receive a differently-worded NOC form for this track β€” download it from your dashboard. If your confirmed start date is on or before 15 July 2026, none of this applies to you β€” you're on the standard track, and the rest of this FAQ governs your internship as usual." + }, + { + "id": "3.1", + "section": "3. NOC (No Objection Certificate)", + "question": "What dates do I put on the NOC?", + "answer": "Default: your chosen start date β†’ your start + 2 months (with up to 1 month grace), ensuring the end date is on or before 31 December 2026. Pick the earliest start date you can realistically make β€” the May–July summer window is the main cohort. If the NOC will be signed on a specific later date, pick a start date after the signature date." + }, + { + "id": "3.2", + "section": "3. NOC (No Objection Certificate)", + "question": "Who can sign the NOC?", + "answer": "Any authorised signatory at your college: HOD, Acting HOD (during holidays), Principal, Dean, Director, or Training & Placement Officer. For dual-degree students, either institution can sign β€” pick whichever is easier. For IITM BS Online Degree (standalone) students, any officer from the BS office can sign." + }, + { + "id": "3.3", + "section": "3. NOC (No Objection Certificate)", + "question": "When do I submit the NOC? Is the deadline hard?", + "answer": "There is no specific calendar cut-off date by which the NOC must be uploaded β€” but your internship cannot formally begin until your official institutional NOC has been uploaded and validated by us. So submit your signed NOC as early as possible and join the current summer cohort. If you are on VINS you can technically upload your NOC and start later in the year, but we strongly do not recommend it β€” by then you will not get to network properly with the rest of the cohort, and the cohort + TA support that make this internship genuinely good are concentrated during the summer window. Submit early, start as soon as possible, and you will get the full version of the experience." + }, + { + "id": "3.4", + "section": "3. NOC (No Objection Certificate)", + "question": "What format should I use? Do I need to design it myself?", + "answer": "No β€” we provide a printable NOC format. Once your result is out and you log in to samagama.in, you will see a Download blank NOC button on your dashboard. Take a printout, get it physically signed and stamped by your authorised signatory, scan it, and upload the signed PDF using the Upload signed NOC button (also on the dashboard). You do not need to draft anything yourself, and you do not need college letterhead β€” the format we provide is the canonical layout." + }, + { + "id": "3.5", + "section": "3. NOC (No Objection Certificate)", + "question": "What if my college / Program Chair gives me an NOC in their own format?", + "answer": "A college's own NOC format is acceptable, as long as all of the required entries are present on it: The signing authority's (HOD / Dean / Program Chair / Principal) handwritten signature β€” this is the most important itemThe signing authority's name, designation, official email address, and phone number (we cross-check with that person to verify the signature is genuine)Your full name and the internship period (start and end dates)Your signature If your college's format does not include a place for your signature, sign clearly and prominently anywhere on the document before uploading. With those entries present, you do not need to switch to our printable format. An NOC missing any of them is incomplete and will be returned for correction." + }, + { + "id": "3.6", + "section": "3. NOC (No Objection Certificate)", + "question": "Does it need to be signed by hand?", + "answer": "Yes. Three things are required, all on the NOC format we provide: The authorised signatory's handwritten signatureThe institutional rubber stamp / seal applied in the designated areaThe signatory's email address filled in the designated field β€” we automatically cross-check with that person to verify the signature is genuine Digital signatures are not accepted. The only path is a physically-signed printout uploaded by you from the dashboard (see Β§3.8)." + }, + { + "id": "3.7", + "section": "3. NOC (No Objection Certificate)", + "question": "Can my HOD email the NOC instead of uploading it?", + "answer": "No. Your NOC must be uploaded by you, the student, from your dashboard β€” we no longer accept NOCs sent by email. We previously offered an email-forward path where your HOD emailed the NOC to us. That option has been retired. NOCs emailed to us β€” whether by you or by your HOD β€” will not be processed. The only accepted way to submit your NOC is to download the format, get it signed by the appropriate authority at your institution, and upload the signed PDF yourself from your dashboard (see Β§3.8). If your college gives you a signed NOC in their own format, that is fine (see Β§3.5) β€” you still upload it yourself as a PDF." + }, + { + "id": "3.8", + "section": "3. NOC (No Objection Certificate)", + "question": "How do I download and upload the NOC?", + "answer": "Both happen on your dashboard at samagama.in once your result is out. You will see a NOC section with two buttons in three places (all backed by the same endpoints β€” use whichever is convenient): A compact pill in the dark header bar at the top of every screen.A standalone NOC card on the dashboard, between the Results card and the Talk-to-Yaksha button. A NOC section at the bottom of your full Result message itself. The two buttons: Download blank NOC β€” saves the printable NOC format PDF.Upload signed NOC (PDF) β€” opens a file picker; the file must be a PDF of at most 1 MB. Confirmation appears on the same card once the upload is received. The chat surface no longer carries any NOC affordance β€” please use the dashboard buttons. If you can't see the buttons, make sure you are logged in as the email that received the result, and that your result has been released." + }, + { + "id": "3.9", + "section": "3. NOC (No Objection Certificate)", + "question": "What if my NOC is not formally verified?", + "answer": "NOC verification takes time β€” typically anywhere between an hour and one full working day from the moment you upload. Your offer letter is issued automatically once your signed institutional NOC is uploaded and validated β€” there is no faster route. (The earlier self-declaration / provisional-offer option was retired on 2026-05-27 and is no longer accepted.) Please upload your signed NOC as early as you can so your start is not delayed." + }, + { + "id": "3.10", + "section": "3. NOC (No Objection Certificate)", + "question": "My online course (Masai, NPTEL, Coursera, etc.) won't issue an NOC. What do I do?", + "answer": "The internship is open only to candidates currently enrolled in a full-time degree programme at a recognised college or university. Online-only courses β€” Masai Institute, NPTEL / MOOC enrolments, Coursera, Udacity, bootcamps, and similar β€” do not by themselves make a candidate eligible. If you are concurrently enrolled in a full-time degree programme alongside the online course, please obtain a No Due / No Objection certificate from that college (department, Dean's office, or Principal) and upload it via the NOC upload flow on your dashboard. If your only current academic engagement is the online course and you are not concurrently enrolled in a full-time degree programme, the internship is not open to you in this cycle. We would warmly welcome you to apply again in a future cycle once you are enrolled in a full-time programme." + }, + { + "id": "3.11", + "section": "3. NOC (No Objection Certificate)", + "question": "My HOD/college official wants written confirmation before signing my NOC. What do I show them?", + "answer": "Your selection is already confirmed the moment your yellow VINS (or green VISE) result panel appears on your samagama.in dashboard β€” that is the official confirmation of your selection, and it is what your HOD should sign your NOC on the basis of. There is no separate written confirmation letter or other proof-of-selection document issued before the NOC step β€” and none can be sent on request. (The selection-confirmation letter and the self-declaration / provisional-offer route have both been discontinued.) Your offer letter is issued only after your signed NOC is uploaded and validated, so it is not available beforehand. If your college will not sign without something in hand, show them your VINS result panel on the dashboard as evidence of selection β€” that is the confirmation we provide." + }, + { + "id": "3.12", + "section": "3. NOC (No Objection Certificate)", + "question": "Can Prof. Sudarshan Iyengar or a faculty member from IIT Ropar sign my NOC for the internship?", + "answer": "Your NOC must be signed by an authorised signatory at the institution where you are enrolled as a student β€” such as your HOD, Dean, Principal, or Training & Placement Officer. Sudarshan Iyengar is a faculty member at IIT Ropar and is not the authorised signatory for the IIT Ropar/Masai online AIML programme. He cannot sign your NOC in a personal capacity. Regarding eligibility: the internship is open to candidates currently enrolled in a UG/PG/Diploma programme at a recognised college or university. An online-only certification course (even if offered jointly with an IIT) does not meet that requirement on its own. If you are concurrently enrolled in a full-time degree programme elsewhere, please obtain the NOC from the authorised signatory at that institution. If your only current academic enrolment is the IIT Ropar/Masai online programme, you are not eligible for this internship cycle. Please clarify your current enrolment status and we will guide you accordingly." + }, + { + "id": "4.1", + "section": "4. Selection, offer letter, and certificate", + "question": "How do I know I am selected?", + "answer": "If you can see your yellow VINS result panel on samagama.in, you are selected. There is no separate selection step or confirmation email." + }, + { + "id": "4.2", + "section": "4. Selection, offer letter, and certificate", + "question": "How do I opt into VINS?", + "answer": "Tell Yaksha in the chat: \"I want to take up the online internship without stipend.\" Yaksha will confirm. Opting in is the selection β€” no separate confirmation email is sent at that stage." + }, + { + "id": "4.3", + "section": "4. Selection, offer letter, and certificate", + "question": "When do I get the offer letter?", + "answer": "Your offer letter is issued automatically once you upload your signed institutional NOC (and have confirmed your start and end dates on the dashboard, see Β§4.5) and we validate it β€” typically within an hour to one full working day of upload. There is a single offer letter on Vicharanashala letterhead; once your NOC is validated it is the operative offer for your college / employer records. (The earlier self-declaration / provisional-offer \"fast path\" was retired on 2026-05-27 and is no longer available β€” a signed institutional NOC is now the only way the offer letter is issued.) The offer letter lives on your dashboard at samagama.in, not in your email. When it is issued, a notification will appear in the Announcements section of samagama.in. Log in and click Download Offer Letter from the Offer Letter card on your dashboard. If you do not see it, do a hard refresh and log back in β€” or simply ask Yaksha in the chat on samagama.in." + }, + { + "id": "4.4", + "section": "4. Selection, offer letter, and certificate", + "question": "Will I get a certificate?", + "answer": "Yes β€” every intern who completes the internship gets a certificate from Vicharanashala, IIT Ropar. The internship is genuinely demanding; candidates who drop out mid-way do not get a certificate. Finishing means something, because the bar is high." + }, + { + "id": "4.5", + "section": "4. Selection, offer letter, and certificate", + "question": "How do I confirm my internship dates?", + "answer": "Once you have opted into VINS in the chat with Yaksha (see Β§4.2), log in to samagama.in. On the dashboard, you will see a yellow card titled \"πŸ—“οΈ Confirm your internship dates\". The two date pickers pre-fill with sensible defaults for the current cohort. If those work for you, hit \"Save dates\". Otherwise edit them to your earliest realistic start β€” your end must be on or before 31 December 2026. A green confirmation appears once saved. You can edit any time from the same card. Order doesn't matter. You can save your dates before or after uploading your NOC β€” both paths work. The dates you enter must match the internship period your HOD signs off on in your NOC. If you need to change the period later, edit the dates on the same card and upload a fresh NOC matching the new period." + }, + { + "id": "4.6", + "section": "4. Selection, offer letter, and certificate", + "question": "I am a minor/major in AI student, can I join the programme? I don't need a NOC as I am from IIT Ropar", + "answer": "Minor/Major in AI course from IIT Ropar is a certification course and there will be a different track of internship equivalent to them. Kindly write to us separately for this. For you to be part of this internship programme you should be a registered student in a UG/PG programme with some university. This internship is exclusively meant for the students only and not for working professionals." + }, + { + "id": "4.7", + "section": "4. Selection, offer letter, and certificate", + "question": "How do I accept the offer letter?", + "answer": "Acceptance happens entirely on your dashboard at samagama.in β€” there is no email reply, and nothing to paste or post. The Offer Letter card guides you through three steps, and your offer is recorded as accepted only once all three are complete. Please finish them within 5 days of your offer being issued: Offer letter β€” download your offer letter, sign the *Acceptance of Offer* block at the bottom of it, and upload the signed PDF on the card. Terms & Conditions (Participation Agreement) β€” read each section and tick the box to confirm you have understood it. Honor Code β€” download the Honor Code, sign the last page, and upload the signed PDF. Each PDF upload is capped at 1 MB; to correct a file, just upload a new copy β€” the latest replaces the previous one. The card updates as you complete each step and shows your offer as accepted once all three are done. There is no acceptance email to send and no statement to type. If you have completed all three steps and the card has not updated after a refresh, log in to samagama.in and describe the issue to Yaksha in the chat β€” Yaksha will flag it for the team." + }, + { + "id": "4.8", + "section": "4. Selection, offer letter, and certificate", + "question": "Can I change my internship dates?", + "answer": "Before the offer letter is issued: yes β€” open the Confirm Internship Dates card on your dashboard and edit the dates at any time. Your end date must be on or before 31 December 2026. After the offer letter is issued: no. Dates are final and will not be changed. If the confirmed dates do not work for you, please follow our LinkedIn page for announcements about future cohorts: linkedin.com/company/vicharanashala If you updated your NOC to reflect new dates before the offer letter was issued, upload the revised NOC via the dashboard." + }, + { + "id": "4.9", + "section": "4. Selection, offer letter, and certificate", + "question": "My NOC is not ready but my start date is approaching. What do I do?", + "answer": "Get your signed institutional NOC uploaded as soon as you can. Your start date cannot be honoured until your official NOC is uploaded and validated by us β€” the internship formally begins only after the NOC is validated. If your NOC is not in by your chosen start date, your start simply shifts to whenever it is validated. (The earlier self-declaration / provisional-offer option was retired on 2026-05-27 and is no longer accepted, so a signed institutional NOC is the only way forward.)" + }, + { + "id": "4.10", + "section": "4. Selection, offer letter, and certificate", + "question": "When does my internship actually begin? Will I receive a notification on the day?", + "answer": "Your internship begins on the start date you confirmed on the dashboard β€” the same date printed on your offer letter β€” provided your official institutional NOC has been uploaded and validated by us by then. If your validated NOC is not yet in on your start date, your start shifts to the day it is validated. There is no separate \"your internship has begun\" email, chat message, or dashboard notification on the day itself; the start date is the start date. On the morning of your start date, log in to samagama.in. Yaksha will guide you through the Day-1 steps of the Bronze phase. If your dashboard appears unchanged on that morning, do a hard refresh and re-login. If it still looks the same, describe the issue to Yaksha in the chat and Yaksha will flag it for the team. You can review or change your confirmed dates via the Confirm Internship Dates card on your dashboard (see Β§4.5 to set, Β§4.8 for date-change rules)." + }, + { + "id": "4.11", + "section": "4. Selection, offer letter, and certificate", + "question": "Can I switch from VINS (online) to VISE (offline) after being selected?", + "answer": "The two tracks are finalised at the interview stage, and we do not move candidates between them. VISE has a fixed on-campus capacity planned around mentor bandwidth, hostel availability, and stipend allocation β€” once the shortlist is set, it stays set. VINS is not a consolation track. The project, the mentor, and the certificate are the same as VISE β€” what differs is the mode (online) and the absence of a fellowship. Many interns find the online format more effective. Your best path forward is to confirm your VINS start dates and get your NOC uploaded β€” you're already in a strong position." + }, + { + "id": "4.12", + "section": "4. Selection, offer letter, and certificate", + "question": "Can I change my internship dates after the offer letter?", + "answer": "No. Once your offer letter has been issued, the dates you confirmed are final. They will not be changed at this stage. If the confirmed dates do not work for you, please follow our LinkedIn page for announcements about future cohorts: linkedin.com/company/vicharanashala" + }, + { + "id": "5.1", + "section": "5. Work and projects", + "question": "What will I work on?", + "answer": "A real open-source project from Vicharanashala's portfolio β€” assigned based on your background and the lab's current needs. Areas range across AI/ML, web development, NLP, computer vision, agriculture-tech (Annam.AI), education-tech (ViBe), and open-source infrastructure. We do not pre-publish the exact problem; you choose to join knowing the lab will assign the project." + }, + { + "id": "5.2", + "section": "5. Work and projects", + "question": "How many hours per day?", + "answer": "Plan for 6 to 10 hours a day, sometimes more during the build phase. This is a full-time internship for the two-month window. Most candidates who drop out are juggling something else β€” VINS expects your full attention. This applies to the standard, live-session track. If you're on the self-paced / parallel track (confirmed start date after 15 July 2026 β€” see Β§2.7), hours are flexible and self-directed instead." + }, + { + "id": "5.3", + "section": "5. Work and projects", + "question": "Who is my mentor?", + "answer": "You will work with the lab's research and engineering team. The exact mentor depends on the project. The model is fluid β€” you will get help from a senior researcher one day, a peer the next, and someone else for a different question. That is how real open-source work happens." + }, + { + "id": "5.4", + "section": "5. Work and projects", + "question": "Is there a stipend?", + "answer": "No. The internship is unpaid. Stellar performers may be recognised with a discretionary stipend at the lab's option, but this is not promised or expected." + }, + { + "id": "5.5", + "section": "5. Work and projects", + "question": "Do I need my own laptop? Should I preload any software?", + "answer": "Yes β€” a personal laptop is required. We prefer that you bring a laptop running Linux or macOS. If you use Windows, please install a terminal that can SSH and run Unix-style commands β€” for example, Windows Subsystem for Linux (WSL) is a clean choice; a tool such as PuTTY also works. You will be SSH-ing into machines and using the command line as part of the work. We do not maintain a fixed software-preload list β€” your mentor will guide you on the specific tools needed once your project is assigned." + }, + { + "id": "5.6", + "section": "5. Work and projects", + "question": "I am using a different email on GitHub / Zoom / the learning platform. Is that okay?", + "answer": "No. Your registered email is your sole identifier across all programme platforms. Progress tracking, course completion, and certificate issuance are all tied to it. Mismatches between platforms cannot be retroactively corrected β€” ensure you use your registered email everywhere from day one." + }, + { + "id": "5.7", + "section": "5. Work and projects", + "question": "When will I be assigned a mentor?", + "answer": "Mentors are not assigned at the start of the internship. You will be assigned a mentor when you move on to the project phase of VINS, which comes later in the timeline. Before that, you must complete the mandatory coursework of the Bronze phase (see Β§1.3). Once coursework is complete and you are placed on a project, your mentor will reach out. If you are looking for a Discord server, please note: we do not run a Discord server. See Β§6.1 for the official communication channels." + }, + { + "id": "6.1", + "section": "6. Code of conduct β€” communication channels", + "question": "What are the official communication channels?", + "answer": "Official channels only. The Announcements section on samagama.in is how we notify you β€” all programme notifications are posted there (we no longer send notifications by email). Log in and check it regularly during working hours; sessions are announced at least 1 hour before they begin. To get help with a question or problem, follow this order: Zoom breakout room. During every live session there is a breakout room β€” use it. Talk to your peers. Most questions are resolved here. Discussion forum. For anything not resolved in the breakout, post on the discussion forum. The link was sent to you in your sign-up (registration) email when you first created your account on samagama.in, and is also posted in the Announcements section. This is where you troubleshoot and discuss with your peers and the team. Yaksha chat on samagama.in. If the forum did not resolve it, bring it to Yaksha. Explain what you tried at each prior step β€” without that, Yaksha cannot help you well. When Yaksha cannot answer, it will flag your query to the team directly and the reply will come back to you through the same chat window. There is no separate ticket form and no email address to write to. Everything goes through Yaksha. WhatsApp support is cancelled. There is no WhatsApp troubleshooting group. Not being on WhatsApp does not put you at any disadvantage β€” all information goes through the channels above. Unofficial groups are strictly prohibited. Creating, joining, or operating any WhatsApp group, Telegram channel, Discord server, or any other peer-coordinated space involving interns or a subset of interns β€” or contacting another intern through their personal phone number β€” is against the code of conduct. Any complaint or report of this will lead to the immediate termination of your internship. You may connect with fellow interns over LinkedIn and email." + }, + { + "id": "7.1", + "section": "7. Interviews Related", + "question": "My interview is not marked as complete on the dashboard β€” what do I do?", + "answer": "A data-sync issue sometimes occurs where the chat session closes but the interview record doesn't update to \"completed.\" The team will check your record and manually mark it as complete if needed. You will be unblocked within 1–2 hours. Apologies for the inconvenience. If you dont hear from us and if your interview continues to be marked incomplete please write to us on internship@vicharanashala.ai" + }, + { + "id": "8.1", + "section": "8. Certificate", + "question": "Does Vicharanashala send a grade report or evaluation to my university for internship credit?", + "answer": "Vicharanashala does not send formal evaluation or grade reports to universities β€” that process is between you and your college. The certificate issued upon completion is the document you can submit to your college or placement office for credit. Whether your college accepts it and how they translate it into a grade is their decision. If your college specifically requires a structured evaluation form or a report on your performance, raise that with them directly β€” we can provide the certificate and, if earned, a letter of recommendation, but we don't have a process for sending grade reports to universities." + }, + { + "id": "8.2", + "section": "8. Certificate", + "question": "Does the Vicharanashala internship certificate specify whether it was completed online or offline ?", + "answer": "The certificate you receive on completing the internship is the same for both tracks. It is issued by Vicharanashala, IIT Ropar, and does not specify whether you completed it online or on campus. The certificate records only that you completed the internship; the mode is not called out separately on the document." + }, + { + "id": "8.3", + "section": "8. Certificate", + "question": "Will the completion certificate be a physical hardcopy or an e-certificate?", + "answer": "The completion certificate is issued as an e-certificate β€” you download it from your dashboard on samagama.in after completing both Bronze and Silver. We do not print and mail physical copies. The certificate is digitally signed and authentic, so it cannot be duplicated. It can also be verified from our database using the number on the certificate." + }, + { + "id": "8.4", + "section": "8. Certificate", + "question": "Is there a WhatsApp group for candidates during the internship?", + "answer": "No. See Β§6.1 for the official communication channels." + }, + { + "id": "9.1", + "section": "9. Rosetta β€” your internship journal", + "question": "What is Rosetta?", + "answer": "Rosetta is your internship journal β€” a 65-day document, one entry per day, every day, for the full duration of Summership 2026. You write in it daily, keep it privately, and submit it at the end of the internship as one of your completion requirements. The name comes from the Rosetta Stone β€” discovered in 1799, it carried the same text in three scripts and became the key to decoding an ancient language that had been silent for centuries. Not because it was grand, but because it was honest and consistent. That is what this journal is meant to be for you. Sixty-five days of honest writing will become the key to understanding your own experience β€” what you learned, where you struggled, what shifted in you." + }, + { + "id": "9.2", + "section": "9. Rosetta β€” your internship journal", + "question": "Why does this exist? Is it just busywork?", + "answer": "No. It exists for two reasons. For you: Most people go through an intense experience and carry it without processing it. They finish and cannot articulate what they actually learned, what changed in them, or what they would do differently. The journal builds that articulation, one day at a time. Students who reflect regularly during a programme consistently get more out of it than those who do not β€” not because they work harder, but because they understand what they are doing and why. For us: When you submit Rosetta at the end, it gives us qualitative insight into your experience that no survey or evaluation can capture. We use it to understand what worked, what did not, and how to make the programme better for the next cohort. Your honest voice matters to that process." + }, + { + "id": "9.3", + "section": "9. Rosetta β€” your internship journal", + "question": "What is a \"thinking routine\"?", + "answer": "Each day in Rosetta has a thinking routine β€” a short framework that gives your reflection a specific shape. Instead of staring at a blank page and writing \"today was good,\" the routine gives you a specific lens. Examples: 3-2-1 β€” 3 things you engaged with, 2 questions on your mind, 1 surprise.Muddy / Clear β€” what is sharp, and what is still foggy.What? So What? Now What? β€” separate facts from meaning from action. The routines rotate across the 65 days so the journal does not feel repetitive or mechanical. Some will feel easy. Some will make you stop and actually think. That is the point. You do not need to research the routine or prepare for it. Just read the description at the top of each day's entry and write." + }, + { + "id": "9.4", + "section": "9. Rosetta β€” your internship journal", + "question": "How do I get my Rosetta journal?", + "answer": "The Rosetta template is here: Rosetta Journal β€” Summership 2026 Open the link, go to File β†’ Make a copy, save it to your own Google Drive, and rename it Rosetta β€” [Your Name] β€” Summership 2026. That copy is yours for the full duration of your internship. Do not write in the shared template. Always work in your own copy." + }, + { + "id": "9.5", + "section": "9. Rosetta β€” your internship journal", + "question": "How do I use it day to day?", + "answer": "Simple: Open your Rosetta Google Doc.Scroll to the entry for today's day number.Fill in the date at the top of the entry.Read the thinking routine name and its one-line description.Answer the three prompts in the writing boxes below.Close it and get on with your day. That is it. It should take between 10 and 20 minutes. It is not an essay. It is not a report. It is a journal." + }, + { + "id": "9.6", + "section": "9. Rosetta β€” your internship journal", + "question": "How long should each entry be?", + "answer": "There is no minimum or maximum word count. A good entry is one that is honest and specific. Three to five sentences per prompt is usually enough. If you find yourself writing more because something genuinely needs unpacking, write more. If a day was quiet and you genuinely only have two sentences, that is fine too. What is not acceptable: one-word answers, copy-pasted text, vague non-answers like \"today was productive,\" or anything that reads like it was generated by an AI." + }, + { + "id": "9.7", + "section": "9. Rosetta β€” your internship journal", + "question": "What is the one rule?", + "answer": "Write what is true. Not what sounds impressive. Not what you think we want to read. Not a polished summary of the day. If you hated today, write that. If you are confused and frustrated, write that. If something clicked and you are genuinely excited, write that. We will know immediately if an entry reads like an LLM wrote it. Do not do that. The journal only counts as a completion requirement if it is genuinely yours β€” in your voice, from your actual experience." + }, + { + "id": "9.8", + "section": "9. Rosetta β€” your internship journal", + "question": "Can I use ChatGPT or any AI tool to write my entries?", + "answer": "No. This is the one firm rule of Rosetta. The journal is a record of your thinking, not a demonstration of what an AI can produce on your behalf. Entries that read as AI-generated will not be counted toward your completion requirement. If your entire journal reads this way, the journal will not be accepted. Use AI tools for your technical work if that is permitted in the programme. Do not use them here." + }, + { + "id": "9.9", + "section": "9. Rosetta β€” your internship journal", + "question": "What if I miss a day?", + "answer": "Fill it in as soon as you can. Write the actual date you are filling it in, not the date of the missed entry. Be honest in the entry about the fact that you are writing it late and why. Do not leave entries blank. A late honest entry is always better than no entry. If you find yourself consistently missing entries, that is worth paying attention to. It usually means something else is going wrong. Use Yaksha in chat, or reach out to your scholar." + }, + { + "id": "9.10", + "section": "9. Rosetta β€” your internship journal", + "question": "Will anyone read my journal during the internship?", + "answer": "No. We will not access your journal during the 65 days. You write it, you keep it, it is yours. The only time we read it is after you submit it at the end of the internship. This is intentional β€” we want you to write freely, without feeling observed. The journal is only useful if it is honest, and it is only honest if you are not performing for an audience." + }, + { + "id": "9.11", + "section": "9. Rosetta β€” your internship journal", + "question": "Can the prompts change mid-internship?", + "answer": "Occasionally we may update a prompt for a specific day based on what is happening in the cohort β€” a major milestone, a collective challenge, or something the team wants to address directly. When this happens, we will announce it in the Announcements section on samagama.in before that day begins. Check the Announcements section before filling any entry where a change has been announced. If no announcement has been made, use the prompt as written in your document." + }, + { + "id": "9.12", + "section": "9. Rosetta β€” your internship journal", + "question": "How do I submit Rosetta at the end?", + "answer": "On or before Day 65, share your Rosetta Google Doc with the programme coordinator's email address (shared during wrap-up week). Set the sharing permission to Viewer. Make sure: Your name is in the document title β€” Rosetta β€” [Your Name] β€” Summership 2026.All 65 entries have been filled in.Your cover page has your name, product, and team filled in. Rosetta submission is one of the required criteria for receiving your internship certificate. An incomplete or AI-generated journal will not be accepted." + }, + { + "id": "9.13", + "section": "9. Rosetta β€” your internship journal", + "question": "I have a question about Rosetta that is not answered here. What do I do?", + "answer": "Ask Yaksha first. If Yaksha cannot answer it, escalate to your programme coordinator. Do not sit on a question β€” the journal works best when you start it right." + }, + { + "id": "9.14", + "section": "9. Rosetta β€” your internship journal", + "question": "My college requires a written confirmation that the internship is self-paced and will not clash with college classes β€” what document can I share with them?", + "answer": "On the standard track, this is not a self-paced internship, but a very rigorous one that is time demanding. It is not permitted for one to be part of any other activity during this period, so no such confirmation can be issued. If your confirmed start date is after 15 July 2026, you are on the genuinely self-paced / parallel track (see Β§2.7) β€” share that section with your college as confirmation instead." + }, + { + "id": "10.1", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "I've already completed a course (Vinternship, Pinternship, MERN, or AI) with you in an earlier cohort β€” am I exempt from repeating it?", + "answer": "Yes. If you completed the Vinternship, Pinternship, MERN, or AI course with us in an earlier cohort, you don't have to repeat it. Submit the exemption form and we'll exempt you from that course. What counts as \"completed\"? A course is treated as complete when your ViBe progress is above 95%. We've set the bar just below 100% so you're not chasing the last percentage point. We verify against your ViBe record before confirming. My progress is below 95% β€” can I still be exempted? No. Below 95% you'll need to finish the remaining portion on ViBe. Once you cross 95%, you're covered. I completed it somewhere else, not on ViBe with VLED β€” does that count? No. This exemption is only for courses you completed with us. If you're a first-timer confident in your fundamentals, the viva route is a separate option β€” check the coursework email for details. How will I know my exemption is approved? We verify submissions against ViBe and confirm on your dashboard. No action is needed from you after submitting. Exemption form: https://forms.gle/RWt1v22yVePyZXD79 > Note: Even if you're exempted from a course, live sessions and stand-ups remain mandatory for everyone on the standard track, regardless of exemption status. No exceptions there. The one exception overall is the self-paced / parallel track (confirmed start date after 15 July 2026 β€” see Β§2.7), which has no mandatory live sessions at all." + }, + { + "id": "10.2", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "How do I register for the AI Fundamentals course on Vibe?", + "answer": "Follow these steps: Click the AI Fundamentals registration link posted in the Announcements section on samagama.in at Phase 1 launch.You'll be redirected to the Vibe sign-in page. If you don't have a Vibe account yet, create one using the same Gmail you used to register on Samagama.Log in with your credentials.After logging in, open the course registration link again in your browser β€” the second click after login is what enrols you.Complete the brief registration form and submit.The course will appear instantly on your Vibe dashboard, ready to watch." + }, + { + "id": "10.3", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "I registered on Vibe with a different email than my Samagama email β€” is that OK?", + "answer": "Please use the same email on both platforms so we can match your Phase 1 progress to your internship record. The one exception: Vibe requires a Gmail address for signup. If the email you used on Samagama is not Gmail (e.g. a college email like @xyz.ac.in, @ds.study.iitm.ac.in), you may use any Gmail of yours to register on Vibe. In that case, tell Yaksha in your Samagama chat using the tag: `` #vibe-email your-gmail@gmail.com `` so we can link the records." + }, + { + "id": "10.4", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "Are live sessions mandatory if I'm on the viva route?", + "answer": "Yes β€” live sessions are mandatory for every intern on the standard track, regardless of path. Whether you're on the coursework track, MERN-exempt (returning intern), or have cleared the viva and moved to Phase 2, you're expected to attend every live session. The exchange of knowledge across our cohort β€” diverse streams, varying levels of experience β€” is something self-paced study cannot replicate. The one exception is the self-paced / parallel track (confirmed start date after 15 July 2026 β€” see Β§2.7): live-session attendance does not apply there." + }, + { + "id": "10.5", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "When and how do I get the Zoom link for the kickoff meeting?", + "answer": "The kickoff orientation is held for the main summer cohort only β€” i.e., candidates starting at the opening of the May–July window. The Zoom link is delivered through two channels: Email to your registered samagama.in address.Your Yaksha chat portal β€” log in to samagama.in, open the chat, and the link is shown there. If your start date is later (mid-summer or beyond), there is no separate kickoff event for you. See Β§2.1 for the trade-offs that come with a later start. If you cannot register with the Zoom link or have not received it, log in to samagama.in and describe the issue to Yaksha in the chat β€” Yaksha will flag it for the team." + }, + { + "id": "10.6", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "How do I get the link for the daily Zoom standups? Are they mandatory?", + "answer": "Daily Zoom standup links are posted in the Announcements section on your samagama.in dashboard β€” look for the announcement bell at the top of the page. You are expected to check it daily before the session. Session links are posted at least 1 hour before they begin. We do not send separate emails for daily standups. The announcement on your dashboard is the only delivery channel for the daily link. Attending the daily standups is mandatory for all interns on the standard track. This is a full-time summer internship programme, and the daily standup is the primary touchpoint where progress, blockers, and the day's plan are communicated. Missing standups is treated as missing work. Attendance and participation are tracked against strict thresholds β€” see Β§10.10. If you're on the self-paced / parallel track (confirmed start date after 15 July 2026 β€” see Β§2.7), you're exempt from live Zoom standups entirely. Instead, you complete the Standup vertical asynchronously: 3,600 cumulative minutes of standup participation, at your own pace. About the kickoff orientation. The kickoff orientation was held for the recommended 15 May cohort (see Β§10.5). Session recordings are not shared with interns who join late β€” see Β§2.6. If you joined late, you are expected to complete the orientation through a special proctored catch-up path on ViBe. The catch-up is entirely proctored and includes quizzes that check whether you have understood the content of the orientation session. Completing this catch-up is mandatory for late starters before participating in the regular standups." + }, + { + "id": "10.7", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "How do I provide my Zoom ID, and why does it matter?", + "answer": "On your dashboard, just before \"Start the internship,\" you'll see a step called \"Provide your Zoom ID.\" Enter the exact email address linked to your Zoom account β€” the one you use (or will use) to join the daily live sessions β€” and save it. This matters because we match your live-session attendance and participation using this email. If the Zoom ID you provide doesn't match the email you actually join Zoom with, your attendance won't be credited to you. So enter it carefully and be sure it is genuinely your Zoom account's email." + }, + { + "id": "10.8", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "I saved the wrong Zoom ID β€” can I change it?", + "answer": "Yes β€” go to your dashboard and look for the lock icon next to your Zoom ID. Click it to unlock the field, update your Zoom email, and save. If you cannot find the toggle or the field is not responding, tell Yaksha your correct Zoom email in the chat and Yaksha will flag it for the team to correct." + }, + { + "id": "10.9", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "Can we register and start the vibe courses before our internship date formally starts?", + "answer": "You will receive the viBE course link only after your internship starts. You can register and start the viBE courses related to the internship only after your internship formally starts." + }, + { + "id": "10.10", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "What are the attendance and participation rules?", + "answer": "These rules apply to the standard, live-session track. If you're on the self-paced / parallel track (confirmed start date after 15 July 2026 β€” see Β§2.7), none of the below applies to you β€” you instead complete the Standup vertical asynchronously. Attendance and participation are tracked strictly, and all of the following are measured continuously over a rolling window of the last 5 working days: Live-session attendance β€” at least 85%. You must be present for at least 85% of the total Zoom meeting time.Live participation β€” at least 85%. You must respond to the in-session polls and quizzes at least 85% of the times they are run.Quizzes β€” attempted, and passed. Every quiz must be attempted, and your pass percentage must be at least 50%. Because this is a rolling 5-working-day measure, it reflects your recent engagement, not a one-time average. If any one of these three falls below its threshold, you will be excused from the current batch and moved to the next batch. This is not a penalty β€” it simply means you rejoin in a later batch where you can give the programme the full attention it needs." + }, + { + "id": "10.11", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "What are Spurti Points (SP)? Do they affect my internship?", + "answer": "Spurti Points are a platform feature that tracks your engagement with the programme. They are currently in an early beta phase β€” not used for any decisions about your standing. See Section 11 for the full SP FAQ." + }, + { + "id": "10.12", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "What are the live-session (Zoom) participation and conduct rules?", + "answer": "Treat every live session as a professional classroom. The following rules apply, and repeated non-compliance may result in removal from a session. Video & visibility Keep your video on at all times unless explicitly permitted otherwise β€” participants with video off will be removed.Your face must be clearly visible β€” well-lit, centred in frame, and not rotated or partially cut off. A forehead-only view, poor lighting, or a sideways angle are not acceptable.Do not join from a mobile phone. Use a laptop or desktop. Audio & environment Test your microphone and video using the platform's audio-test feature before the session begins β€” especially if you plan to speak.Be in a reasonably quiet, well-lit space so you can be seen clearly and participate when called upon. Conduct & attire Dress code: business casuals or neat Indian casuals. Clothing inappropriate for a professional or classroom setting is not allowed.No multitasking. Talking on the phone or with people nearby, and any form of distraction or disengagement, may result in removal. Display name & profile picture Display your full name β€” first and last. Nicknames or partial names are not acceptable.A professional, clearly identifiable profile picture is strongly recommended. This rule is enforced starting the next session (you have until then to update your display name and picture).Use your professional email account to join wherever applicable. Breaks & reconnection For a short break (e.g. a restroom break), indicate \"BRB\" (Be Right Back) and return promptly. Prolonged absence may result in a permanent ban.Breaks are provided periodically β€” manage personal needs during them and avoid leaving during instructional segments.If your internet drops and you are moved to the waiting room, you will not be re-admitted. Ensure you have a stable connection β€” and ideally a backup β€” before joining. Waiting room Participants who do not follow the session guidelines may be moved to the waiting room. Zoom guidelines apply at all times except during official breaks.Do not call or message the Core Team for re-admission from the waiting room β€” doing so will result in an SP deduction.If you believe you were moved by mistake, see Β§10.14 for the dispute process. Speaking effectively Keep responses concise β€” aim for two to three sentences. Rehearse before unmuting, and always test your mic and video beforehand if you plan to speak. Grounds for removal or a permanent ban: video off, joining from mobile, poor visibility, unprofessional attire, disengagement, extended unexplained absence, non-compliance with display-name/picture rules, or repeated violations of session guidelines." + }, + { + "id": "10.13", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "I got \"Failed to submit poll. Error: 100035000\" during a session β€” what does it mean, and will I lose poll credit?", + "answer": "This error appears when the poll closed before your response was submitted β€” either the timer ran out or there was a brief lag between your client and the host. What to do during the session: Do not panic or leave the meeting.If the poll window is still visible on your screen, wait a few seconds and try submitting again.If the error persists and the poll has already closed, note the time and session date β€” you will need this when you report it. To reduce the chance of this happening: Submit your response as soon as the poll appears β€” do not wait until the last moment.Stay attentive throughout the session so you do not miss a poll opening.Keep a backup internet connection ready (e.g., mobile hotspot) in case your primary connection drops.Keep your Zoom app updated to the latest version before each session.Use the Zoom desktop or mobile app, not the browser β€” browser-based Zoom is significantly less reliable for polls." + }, + { + "id": "10.14", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "I was moved to the waiting room β€” what should I do if I think it was a mistake?", + "answer": "Zoom session guidelines are enforced throughout the session, except during official breaks. If you are not following the guidelines, you may be moved to the waiting room. You will not be re-admitted. If you believe you were moved by mistake, you can dispute it: Share a complete screen recording of the session β€” from the time you joined until you were moved to the waiting room β€” showing that you were following all guidelines.Submit it to the Core Team for review via the Discussion Forum or by raising an escalation through Yaksha. Our team will review the recording and assist you accordingly. Disputes without a recording cannot be considered." + }, + { + "id": "10.15", + "section": "10. Phase 1 β€” coursework, Vibe LMS, and live sessions", + "question": "How do I submit my Phase 1 (CSFAQ) project?", + "answer": "Phase 1 project submission is done through the Submit Project button on your Samagama dashboard, which appears once your internship has started. You will submit a GitHub link along with your product documentation and project report. Which GitHub link you submit depends on your assignment: If you were assigned the official CSFAQ repository β€” paste the link to that official repository. You may only submit the repo you were assigned; do not create a separate personal repository.If you were not assigned an official repository β€” you must raise a Pull Request (PR) on an existing open-source public GitHub repository and submit the PR link. Creating a new standalone personal repo does not qualify; your contribution must be a PR on an existing public project. Along with the GitHub link you will also need to upload: Product documentation β€” your Product.md or README.md (PDF or Markdown, max 10 MB).Project report β€” a PDF prepared using the report template shared with you (must include a Feature Spotlight section). One submission per student. Submissions cannot be changed after they are submitted, so review everything carefully before clicking Submit Project." + }, + { + "id": "11.1", + "section": "11. Spurti Points", + "question": "What are Spurti Points?", + "answer": "Spurti Points, or SP, are a points layer on the platform that reflects your overall engagement with the programme. Think of SP as an indicator of your engagement β€” nothing more. It is not a score that defines you as a student or determines your future in the programme." + }, + { + "id": "11.2", + "section": "11. Spurti Points", + "question": "Is SP a finished system?", + "answer": "No. Spurti Points are still being actively built and refined. SP is best understood as a work in progress rather than a finalised grading or evaluation system. The rules and calculations may evolve as the programme develops." + }, + { + "id": "11.3", + "section": "11. Spurti Points", + "question": "How much importance should I give to my SP number?", + "answer": "Please do not read too much into the number. SP is an early beta feature. It is meant to give you a broad sense of your engagement, not to measure your performance or determine outcomes." + }, + { + "id": "11.4", + "section": "11. Spurti Points", + "question": "Can I be terminated or excused because of low SP?", + "answer": "No. The programme team will not terminate, excuse, or take any decision about any intern on the basis of Spurti Points alone. SP is not used as a basis for such decisions." + }, + { + "id": "11.5", + "section": "11. Spurti Points", + "question": "What if my SP shows as zero or even negative?", + "answer": "There is genuinely no cause for concern. A zero or negative SP balance does not mean you are in trouble with the programme. Because SP is in an early beta phase, the number may not always reflect your actual effort or attendance accurately." + }, + { + "id": "11.6", + "section": "11. Spurti Points", + "question": "Does a higher SP bring any benefits?", + "answer": "Yes, in a positive way. Interns who build up higher Spurti Points may, from time to time, become eligible for small perks or recognition from the programme team. Think of a higher SP as a pleasant upside to aim for. SP is not used to punish you for a low number or to decide your standing β€” the one exception is that a mentor may deduct SP for clearly non-compliant behaviour (for example, disrupting a live session or ignoring session conduct rules). That is a deliberate response to conduct, not the same as the beta number simply being low." + }, + { + "id": "11.7", + "section": "11. Spurti Points", + "question": "If SP does not determine outcomes, what does?", + "answer": "Your attendance and live participation are what the programme watches closely and tracks strictly β€” on the standard, live-session track. (The self-paced / parallel track, see Β§2.7, has no live-session attendance requirement at all.) A low SP number is not a cue to ease off. The programme has clear participation requirements that are monitored independently of SP (see Β§10.10 for the exact thresholds)." + }, + { + "id": "11.8", + "section": "11. Spurti Points", + "question": "What are the participation requirements tracked strictly?", + "answer": "This applies to the standard, live-session track only β€” see Β§2.7 if you're on the self-paced / parallel track instead. Looking at your most recent five working days on a rolling basis, every intern (on the standard track) is expected to meet all three of the following: Stay present for at least 85% of the total live Zoom session time.Respond to at least 85% of the polls and quizzes run during the sessions.Attempt every quiz and clear each with a score of at least 50%. All three requirements apply simultaneously. Falling short on even one of them counts." + }, + { + "id": "11.9", + "section": "11. Spurti Points", + "question": "What does \"rolling basis\" mean?", + "answer": "The programme does not look at a single average across the entire duration. Instead, it looks at your most recent five working days at any given point in time. As each new working day passes, the oldest day drops out and the newest day is added. This means your recent, consistent engagement is what counts β€” not a strong performance in one week followed by long absences. The window keeps moving forward." + }, + { + "id": "11.10", + "section": "11. Spurti Points", + "question": "What happens if I fall below the required participation level?", + "answer": "(This applies to the standard, live-session track β€” see Β§2.7 for the self-paced / parallel track, which isn't subject to this.) If any one of the three participation requirements slips below the mark across your most recent five working days, you will be moved from the current batch into a later batch. This is not a termination or an excusal from the programme. It is a practical step so you can rejoin when you are able to give the programme your full attention. To rejoin, you must upload revised documents reflecting your new internship dates: Update your internship dates on the dashboard to match your new batch window.Upload a fresh NOC from your HOD covering the revised period β€” the dates on the NOC must match the dates on your dashboard exactly. Your previous NOC and confirmed dates are tied to the batch you were excused from and cannot be carried forward. A new offer letter will be issued once your revised documents are verified." + }, + { + "id": "11.11", + "section": "11. Spurti Points", + "question": "How are Spurti Points calculated?", + "answer": "Every intern starts with a base of 100 SP credited on their official internship start date. SP is then earned through two live-session activities β€” attendance and poll participation β€” scored independently for every mandatory morning session: A. Attendance Time present (% of session window)SP earned90% or more+10 SP 75% – 89%+5 SP 50% – 74%+3 SP Below 50%0 SP B. Poll participation Polls answered (% of polls launched)SP earned90% or more+10 SP 75% – 89%+5 SP 50% – 74%+3 SP Below 50%0 SP A few things to note: Both A and B are scored per session and summed across all mandatory sessions attended since your start date.There is no penalty for absence β€” missing a session earns 0 for that day, not a deduction.Only the mandatory standup / orientation session (the one flagged mandatory in that day's announcement β€” 9:00–11:00 AM IST through 15 July 2026, 8:00–9:00 PM IST from 16 July 2026 onward) counts. Breakouts, weekend specials, NPTEL/special sessions, and other non-mandatory sessions (including the optional evening motivation/stand-up session) do not contribute to A or B.SP is updated daily. You can see your current balance and a full transaction history on the portal at samagama.in. Important: SP is a reward and recognition feature β€” it is not used by the programme team to judge your engagement, and it plays no role in excusal decisions. Excusal is determined from raw Zoom attendance logs, independently of SP. A low or zero SP number has no bearing on your standing in the programme." + }, + { + "id": "11.12", + "section": "11. Spurti Points", + "question": "Can the programme team award or deduct SP directly?", + "answer": "Yes. In addition to the automatic daily rubric, programme team members can manually award or deduct Spurti Points at their discretion. This is used to: Recognise good behaviour β€” helping peers, exceptional contributions during sessions, or going above and beyond programme expectations.Acknowledge cohort-protocol adherence β€” following session etiquette, forum norms, or other community guidelines exceptionally well.Apply conduct deductions β€” as a deliberate response to disruptive or non-compliant behaviour (for example, repeatedly violating session conduct rules or pestering the core team unnecessarily). Every manual award or deduction is permanently logged in the SP ledger with the awarding team member's identity and a justification note. You can see all entries β€” including manual ones β€” in your SP transaction history on the portal. This does not change the excusal threshold. Excusal is determined solely from raw Zoom attendance logs. A manual SP deduction cannot lead to excusal; it is a recognition/conduct signal, not a standing decision." + }, + { + "id": "12.1", + "section": "12. Yaksha Chat Related", + "question": "I'm unable to type in the chat after clicking 'Interact with Yaksha' β€” what should I do?", + "answer": "The chat input is only active after you have clicked the \"Interact with Yaksha\" button. If you are still unable to type, scroll up to the top of the page β€” the button may be above the visible area. Click it once, and the chat field will become active." + }, + { + "id": "13.1", + "section": "13. ViBe Platform", + "question": "How do I log in to ViBe?", + "answer": "Link for the website: https://vibe.vicharanashala.ai/authSign up as a student with the registered mail ID into the ViBe platform.To log in to the ViBe platform, follow the steps below carefully: ``` Log in to the ViBe platform as a student from registered email IDCheck the \"Notifications\" tab in the Top right side of the Dashboard.Accept the course invite sent for your respective MERN Course. ``` ⚠️ Logging in with a different email ID may result in access issues or missing course visibility." + }, + { + "id": "13.2", + "section": "13. ViBe Platform", + "question": "Invite accepted but shows \"No course enrolled\"?", + "answer": "If you see \"No course enrolled\": Make sure you are logged in with the correct registered email ID.Check if your college email has multiple aliases and try the correct one.Log out and log in again once.Use personal wifi instead of college wifi as there might be some network restrictions of access.If the issue continues, please follow these steps: Step 1: Allow Third-Party Cookies Enable Cookies in Chrome: Open chrome://settings/cookies. Turn OFF \"Block third-party cookies\" and turn ON \"Allow all cookies.\"Add Site Exception: Scroll to \"Sites that can always use cookies\" and click \"Add.\" Paste .vicharanashala.ai and ensure \"Including third-party cookies\"* is enabled.Restart browser. Step 2: Fix DNS (Most Important) Change your laptop DNS to Google DNS.Go to: Control Panel β†’ Network β†’ Active Network β†’ Properties β†’ IPv4.Shortcut: Win + R β†’ ncpa.cpl β†’ right-click properties.Set Preferred DNS to 8.8.8.8 and Alternate DNS to 8.8.4.4.Save. Step 3: Flush Old DNS Cache (it's safe) Open Command Prompt as Admin.Run the following commands:ipconfig /flushdnsipconfig /releaseipconfig /renewRestart WiFi." + }, + { + "id": "13.3", + "section": "13. ViBe Platform", + "question": "Why are videos stuck or repeating?", + "answer": "This may happen due to ViBe's monitored learning system. Common reasons include: Videos must be watched fully and in sequence (no skipping).Camera and microphone permissions must be enabled.Poor lighting or high background noise can affect playback.Switching tabs or staying idle may restart the video. βœ… For smooth playback, stay on the ViBe tab, keep your camera on, and watch videos in a quiet, well-lit environment." + }, + { + "id": "13.4", + "section": "13. ViBe Platform", + "question": "Can I use a mobile or tablet?", + "answer": "No, only desktop/laptop is supported." + }, + { + "id": "13.5", + "section": "13. ViBe Platform", + "question": "I'm experiencing video issues (stuck, looping, skipping) on ViBe. How do I troubleshoot?", + "answer": "Try these troubleshooting steps in order: Refresh the page and check multiple timesInspect browser console: Right-click β†’ Inspect β†’ Go to Network or Console tab β†’ Try watching the video and check for any visible errorsLog out and log in againUse a different browserClear browsing data and cache, then try to re-login If the issue persists after trying all steps, record the issue and describe it to Yaksha in the chat β€” Yaksha will escalate it to the team." + }, + { + "id": "13.6", + "section": "13. ViBe Platform", + "question": "I have completed all videos and quizzes in the ViBe course, but my progress is still showing less than 100%. What should I do?", + "answer": "Please do not worry. This might be a skip made in the quiz/video item due to penalty score as the quiz/video item might not have been successfully completed/marked. Please verify that you've completed all the course items (1006/1006). If not, please retry the missed contents again. In the meantime, you may try the following steps once: Refresh your browserLog out, clear your browser cache, and log in again" + }, + { + "id": "13.7", + "section": "13. ViBe Platform", + "question": "I feel the ViBe content or platform is not good or I am unhappy with the way progress is evaluated. Can I request an exception or bypass the system?", + "answer": "ViBe is built and continuously improved by interns and students themselves. It is a free and open-source learning platform, and our goal is to keep it that way by encouraging the community to actively contribute, improve, and strengthen it rather than bypass it. If a learner strongly feels that the regular ViBe flow does not fairly reflect their understanding, there is a formal alternative evaluation path. However, this path is intentionally rigorous to ensure fairness for everyone. In such cases, you will be asked to: Watch the specified YouTube video content completely (links will be provided).Appear for a three-hour proctored examination based only on that content.Write the exam under strict supervision with:Two cameras (front and side view), andOne online human proctor monitoring you live. This examination becomes the sole basis for evaluation in place of the regular internship track. The scoring rules are strict: Score below 60%: You are considered not qualified and must join the next cohort and continue only through the normal ViBe mode.Score between 60% and 80%: You get one more chance in the next scheduled exam.Score above 80%: You are considered to have passed. This special exam is conducted once every fortnight, so choosing this route may significantly delay your progress compared to continuing normally on ViBe. Because this path is far more demanding and time-consuming than simply completing the regular videos, quizzes, and activities, most students find that continuing with the standard ViBe workflow is the faster and better option." + }, + { + "id": "13.8", + "section": "13. ViBe Platform", + "question": "Is the ViBe consent form compulsory? What if I don't want to grant camera access?", + "answer": "Yes β€” the consent form is compulsory. We would like to clearly inform you that providing consent is a mandatory requirement for any candidate enrolling in and continuing a course on the ViBe Learning Platform. The platform is designed with proctoring enabled throughout the learning process, which requires access to your webcam and microphone. This is essential to ensure: Fairness across all participantsAcademic integrityActive and genuine participation If you choose not to grant camera and microphone access, you will not be able to proceed with the course, as proctoring is an integral part of the learning and evaluation workflow. Privacy & Monitoring Clarification As outlined in the consent form: ViBe does not continuously record videos.Proctoring operates via real-time monitoring mechanisms during learning and assessments.All data is handled strictly in accordance with the stated consent terms and applicable data-protection guidelines. In short, consent is not optional β€” it is a core requirement for participation on the platform." + }, + { + "id": "13.9", + "section": "13. ViBe Platform", + "question": "What are penalty scores on the ViBe platform, and how do they affect our performance or HP?", + "answer": "Penalty scores are generated when anomalies are detected during your activity on the ViBe platform (for example, irregular behavior while watching video lessons or attempting quizzes). If the penalty score becomes high, you may be required to: Rewatch the video lesson, andRetake the associated quiz. At present, these penalty scores do not impact your HP (Health Points) or overall performance evaluation, as they are not being considered for scoring. Their primary purpose is to ensure proper engagement with the learning content." + }, + { + "id": "13.10", + "section": "13. ViBe Platform", + "question": "When should I use the Flag option on ViBe, and when should I contact support?", + "answer": "Use the Flag feature on ViBe only for course content-related issues, such as problems with video content or quiz questions. For technical issues, platform errors, login problems, or logistics-related queries, do not use the flag option. Instead, contact Yaksha so the team can assist you faster. Using the correct method helps resolve issues quickly and keeps the learning process smooth for everyone." + }, + { + "id": "13.11", + "section": "13. ViBe Platform", + "question": "What is Linear Progression on ViBe?", + "answer": "Linear progression is enabled for every course on ViBe. This means each learner must watch the videos and attempt the quizzes in the exact order they appear on the left panel of the course page. In practice: You cannot click on a video or quiz that lies far ahead of your current position.You must complete each item before the next one unlocks.Skipping videos or quizzes is not allowed by design. Progress is sequential β€” finish the item in front of you, and the next one opens up automatically." + }, + { + "id": "13.12", + "section": "13. ViBe Platform", + "question": "Can I use the left navigation panel to jump ahead to a later video or quiz?", + "answer": "No. Although the left navigation panel displays the full list of items in your course, it is meant only as a progress map β€” not as a jump-to-anywhere menu. Instead of clicking through the left panel: Click Next Quiz or Next Lesson as it appears on the right panel.Let the platform unlock items for you in sequence as you complete each one. Trying to skip ahead through the left panel will simply trigger the Access Restricted alert (see 13.13)." + }, + { + "id": "13.13", + "section": "13. ViBe Platform", + "question": "I am seeing a red \"Access Restricted\" banner. Is this a bug?", + "answer": "No, this is not a bug. The red Access Restricted banner is an intentional alert from the platform. The banner looks like this: a red toast notification with an exclamation icon, the title \"Access Restricted\", and the message \"Returning to previous valid content.\" below it. It appears when you try to open an item (video or quiz) before completing all the items that come before it. If you are on the nth item but haven't completed every video and quiz from item 1 through item nβˆ’1, the platform will show this alert. When the banner appears, ViBe automatically returns you to the previous valid content β€” that is, the last item you had legitimately reached in the sequence. You will not lose any progress; you'll simply be sent back to where you actually are in the course. It is the system gently telling you: \"Please check β€” there is something earlier in the course that you haven't finished yet.\"" + }, + { + "id": "13.14", + "section": "13. ViBe Platform", + "question": "How do I resolve the \"Access Restricted\" error?", + "answer": "Follow these steps in order: Go back to the left panel and scroll through your course items from the beginning.Look for any item without a completion tick β€” that is your missed video or quiz.Complete that item (watch the full video, or attempt and submit the quiz).Refresh the page once you've completed all earlier items.If the Access Restricted banner still appears after refreshing and you are sure all earlier items are completed, describe the issue to Yaksha in the chat β€” Yaksha will escalate it to the team. In the large majority of cases, simply finding and completing the missed item clears the alert." + }, + { + "id": "13.15", + "section": "13. ViBe Platform", + "question": "Why does ViBe sometimes make me re-watch a clip after a quiz?", + "answer": "If your answer to the check-in quiz didn't go through correctly, ViBe will take you back to the same clip and let you try again. This is called a re-watch, and it is part of the design β€” not a penalty. A few things to keep in mind: Re-watches are not recorded against you. They do not affect your HP or evaluation.The clips are short, so a re-watch usually costs less time than guessing through multiple retries.Think of the re-watch as the platform helping the idea actually stick before you move on, rather than scolding you for getting it wrong." + }, + { + "id": "13.16", + "section": "13. ViBe Platform", + "question": "What kinds of quiz questions will I see on ViBe?", + "answer": "ViBe quizzes come in four formats: FormatWhat it looks likeWhat to doPick one (MCQ)One right answer out of four or so optionsRead all the options before clicking Pick one or more (MSQ)\"Select all that apply\" β€” could be one correct option or severalRead each option carefully; small mistakes happen here most Type a number (NAT)A text box asking for a numeric valueType just the number β€” no units or extra symbols, unless the question explicitly asks True or FalseA statement with only two options β€” True or FalseRead the statement carefully; a single word like \"always,\" \"never,\" or \"only\" can flip the correct answer A small tip: watch the clip first, then answer. Trying to read the question while the clip is still playing splits your attention." + }, + { + "id": "13.17", + "section": "13. ViBe Platform", + "question": "Are the same proctoring rules applied to every course on ViBe?", + "answer": "No β€” and this is one of the most important things to understand before reading the rest of this section. ViBe's proctoring system is modular. Each individual check β€” face visibility, single-face-in-frame, lighting, background voices, gaze on screen, camera/microphone access β€” can be independently switched on or off by the instructor for a given course or cohort. This means: The instructor decides which proctoring elements are active for their course.Some courses may run with all checks active (typical for internships, faculty FDPs, and credentialed programmes).Other courses may run with only a subset active β€” for example, face visibility on but background-voice detection off β€” depending on the learning context.Certain pilot or open courses may have most proctoring relaxed, especially where the focus is exploration rather than verified evaluation. The FAQs that follow (12.18 through 12.23) describe the full set of proctoring behaviours the platform is capable of. They are written as if everything is switched on. **Whether a specific check applies to your course depends entirely on what your instructor has enabled for that course.** ⚠️ Please do not assume rules carry across courses. A relaxation given in one course (for example, allowing two faces in frame for a paired-learning module) does not transfer to another course on the same platform. Always check the course-specific guidelines shared by your instructor or programme team before each course you join. When in doubt, ask your instructor or programme coordinator which proctoring elements are active for your current course." + }, + { + "id": "13.18", + "section": "13. ViBe Platform", + "question": "What does the \"quiet helper\" on ViBe actually do?", + "answer": "While a lesson plays, a small helper runs gently on your device using your camera and microphone. It checks, in real time, that the basic conditions for learning are present. Specifically, it looks at five things: A face is visible β€” to confirm you are really there.Only one face is in frame β€” to confirm the learning is yours.There is enough light on your face β€” a silhouette is hard to recognise.The room isn't full of voices β€” no talking, TV, or background podcasts.You are looking at the screen β€” brief glances away are fine; long stretches feel like drifting off. The helper is not a judge. Brief, normal movements (a stretch, a sneeze, a glance at your notebook) are absolutely fine. It only pays attention to sustained patterns, not split-second things." + }, + { + "id": "13.19", + "section": "13. ViBe Platform", + "question": "Does ViBe record long videos of me while I'm learning?", + "answer": "No. ViBe does not continuously record videos of you. The camera and microphone are used for real-time presence checks only.Long recordings of your face or voice are not stored.When the lesson ends, the helper goes quiet too.All data is handled strictly in accordance with the consent terms shown when you signed up. Think of the helper as a quiet study partner sitting beside you β€” there if needed, invisible otherwise." + }, + { + "id": "13.20", + "section": "13. ViBe Platform", + "question": "What is the single most common avoidable mistake learners make?", + "answer": "Sitting with a window directly behind you during the day. The room may feel bright to you, but your camera only sees a dark silhouette where your face should be. The helper then struggles to confirm your presence, and your lesson may pause or rewind. The fix is simple: move so the window is to your side or in front of the laptop, not behind you. A lamp placed in front of you works just as well in the evening." + }, + { + "id": "13.21", + "section": "13. ViBe Platform", + "question": "Why does the lesson keep pausing or restarting even when I'm paying attention?", + "answer": "If a clip keeps stopping or going back to the start, the cause is almost always something small in your environment, not the platform itself. Run through this checklist: Your face is too dark β†’ add a lamp in front of you.Your face is partly out of frame β†’ raise the laptop or sit a bit closer to the camera.There are voices in the background β†’ close the door, or move to a quieter room.You switched tabs or went idle β†’ stay on the ViBe tab; take breaks between clips, not during them.Camera or mic permission dropped β†’ check the lock icon in your browser's address bar and confirm both are set to \"Allow\"." + }, + { + "id": "13.22", + "section": "13. ViBe Platform", + "question": "Can I read the quiz questions aloud or mutter to myself while watching?", + "answer": "It's best not to. The microphone listens for sustained voices in the room, and reading aloud, muttering, or asking a roommate \"wait, what was that?\" can all be picked up as anomalies. The simple habit is: watch in silence, answer in silence, and chat freely during your breaks between clips." + }, + { + "id": "13.23", + "section": "13. ViBe Platform", + "question": "Can I study with a friend on camera since we're learning together?", + "answer": "In most courses, no. Only you β€” the registered Learner β€” should be in the camera frame during a lesson. The helper checks that exactly one face is visible at a time. Group study is genuinely a wonderful habit, just not inside a ViBe session itself. A good way to do it: Hop on a separate call with your friend on your phone or a second device.Discuss concepts before or after a ViBe lesson, not during it.Return to ViBe alone when you're ready to watch the clip and attempt the quiz. > πŸ“Œ Note: As explained in 12.17, the single-face-in-frame check is an instructor-controlled setting. A few courses (typically paired-learning or collaborative pilots) may explicitly relax this rule. Do not assume that relaxation in one course applies to another β€” even on the same platform with the same login. Always default to studying alone unless your instructor has clearly stated otherwise for that specific course." + }, + { + "id": "13.24", + "section": "13. ViBe Platform", + "question": "Will I lose my progress if I clear my browser or reinstall it?", + "answer": "No. Your progress is saved on the server, tied to your registered email β€” not on your browser or your computer. So: Refreshing the page, clearing cache, switching browsers, or even reinstalling your browser will not wipe your progress.The moment you log back in with the same registered email, all your completed clips and quizzes will be exactly where you left them. When in doubt: log out, log back in." + }, + { + "id": "13.25", + "section": "13. ViBe Platform", + "question": "Is there a recommended daily learning rhythm on ViBe?", + "answer": "Yes β€” small, regular sessions work far better than long marathon sessions. A practical rhythm: Show up daily, even if only for thirty minutes. Daily consistency beats a five-hour weekend cram.Take breaks between clips, not during them. A clip is short β€” finish it first.Treat each quiz as a gentle check-in, not a high-stakes test.For programmes with deadlines (such as internships or faculty cohorts), aim for the daily progress target announced by your programme team β€” typically around 3.33% per day. > πŸ“Œ A note on milestones: The pacing above is a general guideline only. For any specific programme (Vinternship cohort, faculty FDP, NPTEL internship, institutional pilot, etc.), the actual milestones, deadlines, and weekly progress targets will be announced by your instructors or programme team. Always follow the milestone schedule communicated for your specific cohort β€” that takes precedence over the general guidance here." + }, + { + "id": "13.26", + "section": "13. ViBe Platform", + "question": "What should my \"study corner\" look like before I start a ViBe session?", + "answer": "A ViBe-friendly study spot needs only three things: Light in front of your face β€” a lamp or window facing you, never behind.Just you in the camera frame β€” ask family, friends, or pets not to wander through.A reasonably quiet room β€” no TV, no music with words, no one else on a call nearby. Two minutes of setup before you start saves a lot of small frustrations during the lesson." + }, + { + "id": "13.27", + "section": "13. ViBe Platform", + "question": "I'm facing a technical issue on ViBe that I can't resolve. Is there live support available?", + "answer": "Yes. A Live Support Breakout Session is held every day at 2:00 PM. If you are facing any platform issue on ViBe β€” videos not loading, progress not updating, access errors, login problems, or anything else you cannot resolve on your own β€” join the breakout session and the support team will assist you in real time. This is the fastest way to get a ViBe issue resolved. Please attempt the self-troubleshooting steps in Β§13.5 first, then bring the issue to the 2 PM session if it persists." + }, + { + "id": "14.1", + "section": "14. Team Formation", + "question": "Is team formation compulsory?", + "answer": "No. You can work solo on the FAQ Crowdsourcing project (Phase 1) and on your Phase 2 project β€” teaming up is offered, not required." + }, + { + "id": "14.2", + "section": "14. Team Formation", + "question": "What is the size of a team?", + "answer": "There's no fixed size. Working alone is a complete, valid option. If you'd rather team up, around 10 members is a common size for the Phase 1 FAQ Crowdsourcing team, and Phase 2 project teams can have up to 5 members β€” but these are suggestions, not minimums." + }, + { + "id": "14.3", + "section": "14. Team Formation", + "question": "How are teams formed?", + "answer": "For students who joined on May 15 and 16: Teams were formed through a structured activity.For students joining later: Teams will be randomly assigned by the administration." + }, + { + "id": "14.4", + "section": "14. Team Formation", + "question": "I started on May 15/16 but couldn't form a team during the activity. What happens now?", + "answer": "You will be randomly assigned to a team." + }, + { + "id": "14.5", + "section": "14. Team Formation", + "question": "There was a typo in our email addresses during team formation. Can we fix it?", + "answer": "No action is required from your side. The administration will verify and match email IDs with names before finalizing and locking teams." + }, + { + "id": "14.6", + "section": "14. Team Formation", + "question": "I formed a team with only two members. Will it be considered?", + "answer": "No. Teams with fewer than four members will be expanded by adding additional members to make a final team of four." + }, + { + "id": "14.7", + "section": "14. Team Formation", + "question": "What if a team member leaves or becomes ineligible during Phase 1?", + "answer": "The administration will attempt to assign a replacement member.If no replacement is found, you may continue as a team of three.You must inform the admin immediately, or the change will not be recognized." + }, + { + "id": "14.8", + "section": "14. Team Formation", + "question": "Can I form a team with someone from my own college?", + "answer": "No. Teams must consist of members from different institutions to encourage networking. Exception: Students from the same institution but different campuses/locations may be allowed." + }, + { + "id": "14.9", + "section": "14. Team Formation", + "question": "Can I form a team with students from my IIT MBS cohort?", + "answer": "No. You are encouraged to collaborate with participants outside your existing cohort." + }, + { + "id": "14.10", + "section": "14. Team Formation", + "question": "Can we change our team name after submission?", + "answer": "Yes, team names are tentative and can be changed. However, due to operational constraints, frequent changes are discouraged." + }, + { + "id": "14.11", + "section": "14. Team Formation", + "question": "What if multiple teams choose the same name?", + "answer": "Teams will be distinguished using suffixes (e.g., Team X-1, Team X-2, etc.)." + }, + { + "id": "14.12", + "section": "14. Team Formation", + "question": "What should I do if I face issues within my team?", + "answer": "Report any concerns immediately to your assigned scholar/mentor. Maintaining a safe and respectful environment is a priority." + }, + { + "id": "14.13", + "section": "14. Team Formation", + "question": "How will I know who my mentor is?", + "answer": "Your mentor will be the scholar assigned to the project your team is working on." + }, + { + "id": "14.14", + "section": "14. Team Formation", + "question": "When will I know my team details?", + "answer": "Team details are announced in the Announcements section on samagama.in. Log in and check regularly during working hours." + }, + { + "id": "14.15", + "section": "14. Team Formation", + "question": "I received a team list email but my name is not included. What should I do?", + "answer": "Team announcements are phased, so your name may appear in a later list.If your entire cohort has moved to team activities and you are still unassigned, raise the issue on Yaksha or contact a scholar." + }, + { + "id": "14.16", + "section": "14. Team Formation", + "question": "We selected Project X as our top priority but were assigned Project Y. Can we change it?", + "answer": "No. Project assignments are final and cannot be changed. Allocation is done to ensure balanced distribution across projects." + }, + { + "id": "14.17", + "section": "14. Team Formation", + "question": "I just started the internship. Can I form my own team now?", + "answer": "No. For later cohorts, teams will be randomly assigned. Please wait for the official communication." + }, + { + "id": "14.18", + "section": "14. Team Formation", + "question": "When do team activities begin?", + "answer": "Team-based work begins in Phase 2. During Phase 1 (online coursework), you do not need to worry about team activities." + }, + { + "id": "14.19", + "section": "14. Team Formation", + "question": "Can I request a specific teammate after teams are assigned?", + "answer": "No. Team assignments are final and requests for changes are not entertained." + }, + { + "id": "14.20", + "section": "14. Team Formation", + "question": "What happens if a team member is inactive or not contributing?", + "answer": "You should report the issue to your mentor/scholar early. Prolonged inactivity may lead to administrative intervention." + }, + { + "id": "14.21", + "section": "14. Team Formation", + "question": "Can I switch teams if there are conflicts?", + "answer": "Team switches are not allowed except in exceptional, admin-approved cases involving serious concerns." + }, + { + "id": "14.22", + "section": "14. Team Formation", + "question": "Will team performance affect individual evaluation?", + "answer": "Yes. While some components may be individual, team deliverables are a key part of evaluation." + }, + { + "id": "14.23", + "section": "14. Team Formation", + "question": "How will communication happen within teams?", + "answer": "Teams self-organise internal coordination over LinkedIn or email only, limited to their own team members. WhatsApp is not encouraged for team coordination β€” and it is not permitted to create a team WhatsApp group (a four-person project team is still a \"subset of interns\", which is prohibited under Β§6.1; a group of that form, if reported, will lead to immediate termination of the internship). Official programme updates continue to come through the Announcements section on samagama.in and Yaksha chat β€” see Β§6.1 for the full communication policy." + }, + { + "id": "14.24", + "section": "14. Team Formation", + "question": "What if I miss the team allocation announcement?", + "answer": "All programme updates are posted in the Announcements section on samagama.in. Log in and check regularly during working hours β€” this is the only channel for official notifications." + }, + { + "id": "14.25", + "section": "14. Team Formation", + "question": "Can a team be dissolved and reformed?", + "answer": "No. Once finalized, teams are locked and cannot be dissolved." + }, + { + "id": "14.26", + "section": "14. Team Formation", + "question": "What happens if I drop out of the internship?", + "answer": "Your team will be adjusted accordingly, and the remaining members may continue as a team of three or receive a replacement." + }, + { + "id": "14.27", + "section": "14. Team Formation", + "question": "Will we get time to get to know our teammates before Phase 2?", + "answer": "Yes. There is typically a buffer period before Phase 2 where teams can connect and prepare." + }, + { + "id": "15.1", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "What is this survey that appears when I open Spurti?", + "answer": "It is a short feedback survey about your experience with the Spurti Points system β€” how useful, motivating, fair, and clear you found it. Your honest answers help us improve the programme for current and future students." + }, + { + "id": "15.2", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "Do I have to fill the feedback survey? Is it mandatory?", + "answer": "Yes. For a short window it appears as a pop-up on your Spurti dashboard and must be completed before you continue. It takes only a couple of minutes." + }, + { + "id": "15.3", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "Until when is the feedback survey available?", + "answer": "Until 30 June, 11:59 PM IST. After that the pop-up disappears automatically and you will see your normal Spurti dashboard. Please complete it before the deadline." + }, + { + "id": "15.4", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "Will I keep seeing the survey every time I log in?", + "answer": "No. Once you submit, it never appears again β€” on any device, any future login. Your submission is remembered against your account." + }, + { + "id": "15.5", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "I submitted the survey but it did not close / I still can't see my dashboard.", + "answer": "Try these, in order: Make sure you pressed Submit inside the form.Be signed into your Google account (the survey records your email automatically).Wait a few seconds β€” it closes on its own once your response is recorded.If it still hasn't closed, click \"I've submitted β€” continue.\"Refresh the page." + }, + { + "id": "15.6", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "Is the survey anonymous?", + "answer": "It is confidential, not anonymous. Your response is linked to your programme engagement data so we can analyse patterns properly, but your individual answers are never shared publicly or used for grading. Results are reported only in combined, anonymised form." + }, + { + "id": "15.7", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "Which email should I use for the survey?", + "answer": "Use the email you registered with for the programme. The form records your email automatically from your signed-in Google account, so please be signed in with the correct account before submitting." + }, + { + "id": "15.8", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "Why is my Spurti dashboard hidden behind the survey?", + "answer": "That is intentional, only for this short window, to make sure every student's feedback is captured. It is not a bug. After you submit (or after the deadline), your full dashboard returns to normal." + }, + { + "id": "15.9", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "What is a Level?", + "answer": "Your Level is a lifetime achievement marker. It reflects the highest SP you have ever reached in the programme β€” your best-ever engagement." + }, + { + "id": "15.10", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "How is my Level calculated?", + "answer": "Level = highest-ever SP Γ· 100 (rounded down). So 0–99 highest SP = Level 0, 100–199 = Level 1, 200–299 = Level 2, and so on. Every extra 100 SP you have ever reached is one more Level." + }, + { + "id": "15.11", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "Can my Level go down if my current SP drops?", + "answer": "No. Level is based on your highest-ever SP, so it never decreases. Even if your current balance falls, the Level you earned stays with you permanently." + }, + { + "id": "15.12", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "What is a Trophy League?", + "answer": "Your Trophy League shows your current standing right now, based on your current SP balance. Unlike Level, it can move up or down as your SP changes β€” it reflects how you are doing at the moment." + }, + { + "id": "15.13", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "What are the Trophy League bands?", + "answer": "Current SPTrophy League1500+Legend 1400–1499Diamond I 1300–1399Diamond II 1200–1299Diamond III 1100–1199Platinum I 1000–1099Platinum II 900–999Platinum III 800–899Gold I 700–799Gold II 600–699Gold III 500–599Silver I 400–499Silver II 300–399Silver III 200–299Bronze I 100–199Bronze II 0–99Bronze III" + }, + { + "id": "15.14", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "What is the difference between Level and Trophy League?", + "answer": "Level = your best ever β€” a lifetime badge that never goes down.Trophy League = your current SP standing β€” it rises and falls with your balance. You can be a high Level (because you once reached a high SP) but currently sit in a lower League (because your current SP has dropped). Staying active keeps both strong." + }, + { + "id": "15.15", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "What is Legend?", + "answer": "Legend is the top Trophy League, reached when your current SP is 1500 or more." + }, + { + "id": "15.16", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "What is the Legend Badge, and is it permanent?", + "answer": "Once your highest-ever SP reaches 1500, you unlock the Legend Badge β€” and it is permanent. Even if your current SP later drops below 1500 (so your League changes), the Legend Badge stays with you forever as recognition that you reached the top." + }, + { + "id": "15.17", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "There are two leaderboards β€” what is the difference?", + "answer": "Overall β€” everyone in the programme, ranked by current SP. This is the default.My Onboarding Group β€” only students who started around the same time as you." + }, + { + "id": "15.18", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "Why is there an onboarding-group leaderboard?", + "answer": "Students join the programme on different dates. Someone who started earlier has had more sessions to earn SP from, so an overall comparison is not always fair to recent joiners. The onboarding-group view compares you with people who started in the same two-week window, which is a fairer like-for-like comparison." + }, + { + "id": "15.19", + "section": "15. Spurti Levels, Trophy Leagues & Feedback Survey", + "question": "How are onboarding groups decided?", + "answer": "By the half-month in which you started: the 1st–15th of a month is one group, the 16th–end of month is the next. Your group is shown as a date range, e.g. \"2026-06-01 to 2026-06-15\". Levels, Trophy Leagues, and Legend are all derived views over your existing Spurti Points β€” they do not add or remove any SP. Keep attending and participating, and all three grow together." } ] } diff --git a/apps/backend/src/integrations/discord/botManager.ts b/apps/backend/src/integrations/discord/botManager.ts index 3500f4a6..f2275201 100644 --- a/apps/backend/src/integrations/discord/botManager.ts +++ b/apps/backend/src/integrations/discord/botManager.ts @@ -29,6 +29,7 @@ import { Client, GatewayIntentBits, Events, Partials } from 'discord.js'; import { Types } from 'mongoose'; import { registerCommands } from './registerCommands.js'; import { handleInteraction } from './events/interactionCreate.js'; +import { handleMessageReaction } from './discordIngestionService.js'; import { logger } from '../../utils/http/logger.js'; import { decrypt } from '../../utils/auth/crypto.js'; @@ -105,8 +106,13 @@ class BotManager { return null; } const client = new Client({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages], - partials: [Partials.Channel], + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMessageReactions + ], + partials: [Partials.Channel, Partials.Message, Partials.Reaction], }); return { batchId, client, config: cfg, startedAt: new Date() }; } @@ -154,6 +160,9 @@ class BotManager { // command set. Phase 6+ adds per-guildβ†’batchId routing. void handleInteraction(interaction, instance!.config); }); + instance.client.on(Events.MessageReactionAdd, (reaction, user) => { + void handleMessageReaction(reaction, user, instance!.config); + }); await instance.client.login(instance.config.botToken); this.bots.set(batchId, instance); return instance; diff --git a/apps/backend/src/integrations/discord/discordIngestionService.ts b/apps/backend/src/integrations/discord/discordIngestionService.ts new file mode 100644 index 00000000..68534129 --- /dev/null +++ b/apps/backend/src/integrations/discord/discordIngestionService.ts @@ -0,0 +1,140 @@ +import { MessageReaction, User, Message, PartialMessageReaction, PartialUser } from 'discord.js'; +import { logger } from '../../utils/http/logger.js'; +import { chatWithConfig, getPipelineProviderConfig } from '../../utils/ai/aiProvider.js'; +import FAQ from '../../modules/faq/faq.model.js'; +import { Types } from 'mongoose'; +import { ProgramBotConfig } from './botManager.js'; + +export async function handleMessageReaction( + reaction: MessageReaction | PartialMessageReaction, + user: User | PartialUser, + config: ProgramBotConfig +): Promise { + if (user.bot) return; + + // We are looking for a specific emoji to trigger ingestion. Let's use βœ… or πŸ’‘ + const triggerEmojis = ['βœ…', 'πŸ’‘']; + if (!triggerEmojis.includes(reaction.emoji.name ?? '')) return; + + try { + // Ensure we have the full message + if (reaction.partial) { + await reaction.fetch(); + } + const message = reaction.message; + if (message.partial) { + await message.fetch(); + } + + if (message.author?.bot) return; + + logger.info(`[discordIngestion] Triggered by ${user.tag} on message ${message.id} in guild ${config.guildId}`); + + // Fetch conversation context. If it's a thread, get the original message. + let questionText = ''; + let answerText = message.content; + + if (message.channel.isThread()) { + const starterMessage = await message.channel.fetchStarterMessage(); + if (starterMessage) { + questionText = starterMessage.content; + } + } else if (message.reference && message.reference.messageId) { + try { + const referencedMessage = await message.channel.messages.fetch(message.reference.messageId); + if (referencedMessage) { + questionText = referencedMessage.content; + } + } catch (err) { + logger.warn(`[discordIngestion] Failed to fetch referenced message ${message.reference.messageId}: ${(err as Error).message}`); + questionText = "Extract the question from the context."; + } + } else { + // Not a thread or reply. We will assume the message itself contains both or just extract what we can. + questionText = "Extract the question from the context."; + } + + const systemPrompt = `You are an AI assistant tasked with extracting a clean, formal Question and Answer from a Discord support conversation. +The user has reacted with a checkmark to indicate this message resolves a question. +Extract the core question and the core answer. +Format your response as a JSON object with strictly two keys: "question" and "answer". Do not include any other text. +If you cannot find a valid question/answer, return {"question": "Could not extract", "answer": "Could not extract"}.`; + + const userPrompt = ` +Context Question (if any): ${questionText} +Answer Message: ${answerText} + `; + + const aiConfig = await getPipelineProviderConfig('auto_answer'); + const rawReply = await chatWithConfig(aiConfig, [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userPrompt } + ]); + + let parsed: { question?: string, answer?: string } = {}; + try { + // find JSON boundaries in case of markdown wrapping + const jsonStr = rawReply.replace(/```json/g, '').replace(/```/g, '').trim(); + parsed = JSON.parse(jsonStr); + } catch (e) { + logger.warn(`[discordIngestion] Failed to parse AI response as JSON: ${rawReply}`); + return; + } + + if (!parsed.question || !parsed.answer || parsed.question === "Could not extract") { + logger.warn(`[discordIngestion] AI could not extract valid Q&A from message ${message.id}`); + return; + } + + // Save to FAQ collection as pending_review + const faq = await FAQ.create({ + question: parsed.question, + answer: parsed.answer, + tags: ['discord-ingestion'], + category: 'General', + searchCount: 0, + status: 'pending', + views: 0, + helpfulVotes: 0, + unhelpfulVotes: 0, + createdBy: null, // System generated + freshnessTier: 'evergreen', + reviewIntervalDays: 0, + reviewStatus: 'pending_review', + lastVerifiedDate: new Date(), + flaggedAt: new Date(), + flagType: 'auto', + flagReason: 'Ingested from Discord via reaction', + flaggedBy: null, + reviewCycle: 1, + lastCheckedAt: new Date(), + trustLevel: 'medium', + sourceType: 'manual', + sourceCommunityPostId: null, + promotedAt: null, + objectionStatus: 'none', + promotionMetadata: null, + sourceMeetingId: null, + sourceMeetingTopic: null, + sourceInsightId: null, + batchId: new Types.ObjectId(config.batchId), + categoryId: null, + popularityScore: 0, + guestViewCount: 0, + avgReadCompletion: 0, + avgTimeSpentRatio: 0, + guestViewLast24h: 0, + wordCount: (parsed.question.length + parsed.answer.length) / 5, + expectedReadMs: ((parsed.question.length + parsed.answer.length) / 5) * 300, + popularityUpdatedAt: null, + }); + + logger.info(`[discordIngestion] Successfully ingested FAQ ${faq._id} from message ${message.id}`); + + // React to the message to show success + await message.react('πŸ’Ύ'); + + } catch (err) { + logger.error(`[discordIngestion] Error handling reaction: ${(err as Error).message}`); + } +} diff --git a/apps/backend/src/middleware/programScope.ts b/apps/backend/src/middleware/programScope.ts index 8901f86a..02efdea3 100644 --- a/apps/backend/src/middleware/programScope.ts +++ b/apps/backend/src/middleware/programScope.ts @@ -82,7 +82,13 @@ export function programScope(opts: { required?: boolean } = {}) { } try { - const batch = await Batch.findById(batchId).select('_id name isActive').lean(); + let batch = await Batch.findById(batchId).select('_id name isActive').lean(); + if (!batch) { + batch = await Batch.findOne({ isDefault: true }).select('_id name isActive').lean(); + } + if (!batch) { + batch = await Batch.findOne({ isActive: true }).select('_id name isActive').lean(); + } if (!batch) { res.status(404).json({ message: 'Program not found.' }); return; diff --git a/apps/backend/src/modules/admin/admin-welcome.controller.ts b/apps/backend/src/modules/admin/admin-welcome.controller.ts index e4d01c8d..04ec35e8 100644 --- a/apps/backend/src/modules/admin/admin-welcome.controller.ts +++ b/apps/backend/src/modules/admin/admin-welcome.controller.ts @@ -12,7 +12,7 @@ import ZoomSession from '../zoom/zoom-session.model.js'; import FAQ from '../faq/faq.model.js'; import AiClient from '../ai/ai-client.service.js'; import { generateEmbedding } from '../../utils/ai/embeddings.js'; -import { MarkItDown } from 'markitdown-ts'; +// MarkItDown is imported lazily to avoid pdfjs-dist loading at startup (DOMMatrix crash on Node 18) import path from 'path'; import os from 'os'; import { publicAssetUrl, publicBasePath } from '../../utils/publicBasePath.js'; @@ -810,6 +810,7 @@ export const uploadZoomSessionTranscript = async (req: Request, res: Response): let textContent = ''; if (originalname.endsWith('.pdf') || mimetype === 'application/pdf') { + const { MarkItDown } = await import('markitdown-ts'); const md = new MarkItDown(); const tempPath = path.join(os.tmpdir(), `transcript-${Date.now()}.pdf`); fs.writeFileSync(tempPath, buffer); diff --git a/apps/backend/src/modules/program/feature-flag.controller.ts b/apps/backend/src/modules/program/feature-flag.controller.ts index ce67336f..9c3a2536 100644 --- a/apps/backend/src/modules/program/feature-flag.controller.ts +++ b/apps/backend/src/modules/program/feature-flag.controller.ts @@ -50,7 +50,7 @@ export const FEATURE_FLAGS = { description: 'The floating AskAI assistant button shown on non-admin pages. When disabled, ' + 'the button is hidden from the UI. Toggle on to re-enable the chatbot for users.', - defaultEnabled: false, + defaultEnabled: true, }, aiAutoAnswer: { label: 'AI Auto-Answer Queue', diff --git a/apps/backend/src/modules/program/registration-config.model.ts b/apps/backend/src/modules/program/registration-config.model.ts index 093a35bd..2926dfc0 100644 --- a/apps/backend/src/modules/program/registration-config.model.ts +++ b/apps/backend/src/modules/program/registration-config.model.ts @@ -53,8 +53,8 @@ export interface IRegistrationConfig extends Document { const registrationConfigSchema = new MongooseSchema( { _id: { type: String, default: 'singleton' }, - registrationEnabled: { type: Boolean, default: false }, - openForAll: { type: Boolean, default: false }, + registrationEnabled: { type: Boolean, default: true }, + openForAll: { type: Boolean, default: true }, inviteToken: { type: String, required: true }, tokenGeneratedAt: { type: Date, required: true }, lastToggledBy: { @@ -97,8 +97,8 @@ export async function ensureRegistrationConfig(): Promise { const token = generateInviteToken(); doc = await RegistrationConfigModel.create({ _id: 'singleton', - registrationEnabled: false, - openForAll: false, + registrationEnabled: true, + openForAll: true, inviteToken: token, tokenGeneratedAt: new Date(), lastToggledBy: null, @@ -108,6 +108,10 @@ export async function ensureRegistrationConfig(): Promise { console.warn( `\n[registrationConfig] First-time init β€” invite token (copy now, won't be shown again):\n${token}\n` ); + } else if (!doc.registrationEnabled || !doc.openForAll) { + doc.registrationEnabled = true; + doc.openForAll = true; + await doc.save(); } return doc; } \ No newline at end of file diff --git a/apps/backend/src/scripts/seed.ts b/apps/backend/src/scripts/seed.ts index de64c1a2..04b54e84 100644 --- a/apps/backend/src/scripts/seed.ts +++ b/apps/backend/src/scripts/seed.ts @@ -99,6 +99,9 @@ const seed = async () => { console.log(` βœ“ Default batch already exists: "${defaultBatch.name}" (${defaultBatch._id})`); } + // Clear existing FAQs to ensure a clean seed with the correct live FAQs + await FAQ.deleteMany({}); + // Upsert FAQs from faqs.json console.log('[3/3] Seeding FAQs...'); const faqPath = path.join(__dirname, '..', 'faqs.json'); diff --git a/apps/backend/src/scripts/seedPosts.ts b/apps/backend/src/scripts/seedPosts.ts index 90fc8b06..b6eb7dc7 100644 --- a/apps/backend/src/scripts/seedPosts.ts +++ b/apps/backend/src/scripts/seedPosts.ts @@ -14,6 +14,8 @@ import User from '../modules/auth/user.model.js'; import { generateEmbedding } from '../utils/ai/embeddings.js'; import readline from 'readline'; +import Batch from '../modules/program/batch.model.js'; + interface SamplePost { title: string; body: string; status: string; answer: string | null; } const samplePosts: SamplePost[] = [ @@ -41,10 +43,13 @@ async function seedPosts() { } await mongoose.connect(process.env.MONGODB_URI); - const author = await User.findOne({ email: 'reg@yaksha.com' }) ?? (await User.findOne()); + const author = await User.findOne({ email: 'admin@yaksha.com' }) ?? (await User.findOne()); if (!author) { console.error('No user found. Run seed.ts first.'); process.exit(1); } - if (!process.argv.includes('--keep')) { + const defaultBatch = await Batch.findOne({ isDefault: true }) ?? (await Batch.findOne()); + const batchId = defaultBatch?._id ?? null; + + if (!process.argv.includes('--keep') && process.stdin.isTTY) { const count = await CommunityPost.countDocuments(); if (count > 0) { console.log(`WARNING: Will delete ${count} existing posts. Use --keep to skip clearing.`); @@ -53,13 +58,15 @@ async function seedPosts() { await CommunityPost.deleteMany({}); console.log('Cleared existing posts.'); } + } else if (!process.argv.includes('--keep')) { + await CommunityPost.deleteMany({}); } let inserted = 0; for (const post of samplePosts) { try { const embedding = await generateEmbedding(`Question: ${post.title}. Description: ${post.body}. Answer: ${post.answer ?? ''}`); - await CommunityPost.create({ ...post, author: author._id, embedding }); + await CommunityPost.create({ ...post, author: author._id, batchId, embedding }); inserted++; console.log(` + ${post.title}`); } catch (err) { diff --git a/apps/backend/src/server.ts b/apps/backend/src/server.ts index ba521d85..35853523 100644 --- a/apps/backend/src/server.ts +++ b/apps/backend/src/server.ts @@ -1,3 +1,7 @@ +import crypto from 'node:crypto'; +if (typeof globalThis.crypto === 'undefined') { + globalThis.crypto = crypto.webcrypto as any; +} import './env.js'; import { validateEnv } from './config/envValidator.js'; import { loadConfig } from './config/loader.js'; diff --git a/apps/backend/src/utils/ai/embeddings.ts b/apps/backend/src/utils/ai/embeddings.ts index eda7d04c..ce34a71a 100644 --- a/apps/backend/src/utils/ai/embeddings.ts +++ b/apps/backend/src/utils/ai/embeddings.ts @@ -80,13 +80,14 @@ export const warmEmbedder = async (): Promise => { * Generate an embedding for a DOCUMENT (FAQ, post, etc.). */ export const generateEmbedding = async (text: string, options?: { batchId?: string | null }): Promise => { + const { dimensions, model, baseURL, apiKey } = await getActiveEmbeddingConfig(options?.batchId); + if (process.env.FAST_SEED === 'true' || apiKey === 'ollama') { + return new Array(dimensions).fill(0); + } try { - const { model, baseURL, apiKey } = await getActiveEmbeddingConfig(options?.batchId); return await callCustomEmbedding(text, apiKey, model, baseURL); } catch (err) { if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') { - const { dimensions } = await getActiveEmbeddingConfig(options?.batchId); - logger.warn(`[embeddings] Custom embedding API failed, falling back to mock vector: ${(err as Error).message}`); return new Array(dimensions).fill(0); } throw err; diff --git a/apps/backend/src/utils/documentExtractor.ts b/apps/backend/src/utils/documentExtractor.ts index 8a3315d4..812a269e 100644 --- a/apps/backend/src/utils/documentExtractor.ts +++ b/apps/backend/src/utils/documentExtractor.ts @@ -17,7 +17,9 @@ */ import { createWorker, type Worker } from 'tesseract.js'; -import { MarkItDown } from 'markitdown-ts'; +// markitdown-ts is imported lazily (dynamic import) to avoid pdfjs-dist +// loading at startup β€” pdfjs-dist requires DOMMatrix which is not available +// in Node 18. The dynamic import defers the cost to the first PDF conversion. import { logger } from './http/logger.js'; export type DocumentFileType = 'image' | 'pdf' | 'docx' | 'xlsx'; @@ -63,9 +65,16 @@ export async function shutdownTesseract(): Promise { } } -// ─── MarkItDown singleton ──────────────────────────────────────────────────── +// ─── MarkItDown singleton (lazy) ──────────────────────────────────────────── -const _markitdown = new MarkItDown(); +let _markitdown: InstanceType | null = null; +async function getMarkItDown() { + if (!_markitdown) { + const { MarkItDown } = await import('markitdown-ts'); + _markitdown = new MarkItDown(); + } + return _markitdown; +} // ─── Public entry point ────────────────────────────────────────────────────── @@ -101,7 +110,8 @@ export async function extractTextFromFile( // bare form and falls through to the text converter, which chokes // on the `%PDF-1.3` binary header. const ext = `.${fileType}`; - const result = await _markitdown.convertBuffer(buffer, { + const markitdown = await getMarkItDown(); + const result = await markitdown.convertBuffer(buffer, { file_extension: ext, }); return { diff --git a/apps/backend/src/vitest.setup.ts b/apps/backend/src/vitest.setup.ts new file mode 100644 index 00000000..390fa0fc --- /dev/null +++ b/apps/backend/src/vitest.setup.ts @@ -0,0 +1,5 @@ +import crypto from 'node:crypto'; + +if (typeof globalThis.crypto === 'undefined') { + globalThis.crypto = crypto.webcrypto as any; +} diff --git a/apps/backend/vitest.config.ts b/apps/backend/vitest.config.ts index 9d4be12b..4ff54bec 100644 --- a/apps/backend/vitest.config.ts +++ b/apps/backend/vitest.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ environment: 'node', globals: true, fileParallelism: false, + setupFiles: ['./src/vitest.setup.ts'], include: ['**/__tests__/**/*.ts', '**/*.test.ts'], coverage: { provider: 'v8', diff --git a/apps/frontend/src/admin/pages/AdminAISettings.tsx b/apps/frontend/src/admin/pages/AdminAISettings.tsx index d8992182..723db9f0 100644 --- a/apps/frontend/src/admin/pages/AdminAISettings.tsx +++ b/apps/frontend/src/admin/pages/AdminAISettings.tsx @@ -694,6 +694,7 @@ export default function AdminAISettings() {
{(Object.keys(FEATURE_LABELS) as Array).map((feature) => { const f = features[feature]; + if (!f) return null; return (
diff --git a/apps/frontend/src/admin/pages/AdminProgramSettingsPage.tsx b/apps/frontend/src/admin/pages/AdminProgramSettingsPage.tsx index 7bfbb36a..9e7b1ff8 100644 --- a/apps/frontend/src/admin/pages/AdminProgramSettingsPage.tsx +++ b/apps/frontend/src/admin/pages/AdminProgramSettingsPage.tsx @@ -67,7 +67,7 @@ export default function AdminProgramSettingsPage() { setForm(res.data.settings); } } catch (e: unknown) { - if (e instanceof Error && e.name === 'CanceledError') return; + if (e instanceof Error && (e.name === 'CanceledError' || (e as any).code === 'ERR_CANCELED')) return; setError('Could not load program settings.'); } finally { setLoading(false); diff --git a/apps/frontend/src/components/layout/NavPills.tsx b/apps/frontend/src/components/layout/NavPills.tsx index 375d94bd..1cffcd6b 100644 --- a/apps/frontend/src/components/layout/NavPills.tsx +++ b/apps/frontend/src/components/layout/NavPills.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { NavLink } from 'react-router-dom'; +import { NavLink, useNavigate } from 'react-router-dom'; import { useAuth } from '../../hooks/useAuth'; import { useFeatureFlag } from '../../context/FeatureFlagContext'; +import { useAuthGate } from '../../context/AuthModalContext'; export type NavItem = { label: string; to: string; xlOnly?: true }; @@ -47,8 +48,10 @@ export function useNavItems() { } export function NavPills() { - const { user } = useAuth(); + const { user, isAuthenticated } = useAuth(); const allNavItems = useNavItems(); + const gate = useAuthGate(); + const navigate = useNavigate(); return (
@@ -56,11 +59,21 @@ export function NavPills() { const isWelcome = to === '/welcome'; const needsPulse = isWelcome && user && !user.orientationCompleted; + const handleClick = (e: React.MouseEvent) => { + if (isWelcome && !isAuthenticated) { + e.preventDefault(); + gate(() => { + navigate('/welcome'); + }, 'Sign in to access the Welcome Package.')(); + } + }; + return ( `nav-pill relative ${isActive ? 'active' : ''} ${xlOnly ? 'hidden xl:inline-flex' : ''} ${needsPulse && !isActive ? 'animate-pulse text-[rgb(var(--accent-rgb))] shadow-[inset_0_0_15px_rgb(var(--accent-rgb)_/_0.15)] bg-[rgb(var(--accent-rgb)_/_0.05)]' : ''}` } @@ -77,3 +90,4 @@ export function NavPills() {
); } + diff --git a/apps/frontend/src/components/layout/Navbar.tsx b/apps/frontend/src/components/layout/Navbar.tsx index a6e10251..29c53704 100644 --- a/apps/frontend/src/components/layout/Navbar.tsx +++ b/apps/frontend/src/components/layout/Navbar.tsx @@ -73,6 +73,7 @@ export default function Navbar({ showProgramSwitcher: _showProgramSwitcher = fal const { user, isAuthenticated, logout } = useAuth(); const { openModal } = useAuthModal(); + const gate = useAuthGate(); const navigate = useNavigate(); const location = useLocation(); const [scrolled, setScrolled] = useState(false); @@ -334,23 +335,35 @@ export default function Navbar({ showProgramSwitcher: _showProgramSwitcher = fal
)} - {allNavItems.map(({ label, to }) => ( - setMobileOpen(false)} - className={({ isActive }) => - `block px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ${ - isActive - ? 'bg-accent-light text-accent' - : 'text-ink-soft hover:text-ink hover:bg-black/[0.03]' - }` + {allNavItems.map(({ label, to }) => { + const isWelcome = to === '/welcome'; + const handleMobileClick = (e: React.MouseEvent) => { + setMobileOpen(false); + if (isWelcome && !isAuthenticated) { + e.preventDefault(); + gate(() => { + navigate('/welcome'); + }, 'Sign in to access the Welcome Package.')(); } - > - {label} - - ))} + }; + return ( + + `block px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 ${ + isActive + ? 'bg-accent-light text-accent' + : 'text-ink-soft hover:text-ink hover:bg-black/[0.03]' + }` + } + > + {label} + + ); + })} {/* Mobile: Sign-in / Get started */} {!isAuthenticated && ( diff --git a/apps/frontend/src/context/FeatureFlagContext.tsx b/apps/frontend/src/context/FeatureFlagContext.tsx index c439f0c2..ee58791a 100644 --- a/apps/frontend/src/context/FeatureFlagContext.tsx +++ b/apps/frontend/src/context/FeatureFlagContext.tsx @@ -75,7 +75,24 @@ export function FeatureFlagProvider({ children }: ProviderProps): React.ReactEle setError(null); setLoading(true); if (!isAuthenticated) { - setFlags({}); + setFlags({ + askAiChatbot: { + key: 'askAiChatbot', + enabled: true, + label: 'Ask AI Chatbot', + description: 'Floating AI assistant widget', + firstEnabledAt: null, + lastDisabledAt: null, + }, + welcomePackage: { + key: 'welcomePackage', + enabled: true, + label: 'Welcome Package', + description: 'The student onboarding / orientation hub', + firstEnabledAt: null, + lastDisabledAt: null, + }, + }); setLoading(false); return; } diff --git a/apps/frontend/src/context/ProgramContext.tsx b/apps/frontend/src/context/ProgramContext.tsx index e437b136..4ea6f7c5 100644 --- a/apps/frontend/src/context/ProgramContext.tsx +++ b/apps/frontend/src/context/ProgramContext.tsx @@ -117,10 +117,10 @@ export function ProgramProvider({ children }: ProgramProviderProps): React.React const loadPrograms = useCallback(async (): Promise => { try { const res = await api.get<{ batches: Program[] }>('/batches'); + setError(null); return res.data.batches ?? []; - } catch { - // Non-fatal: the public page can still render with an empty list - // and a friendly "no programs" empty state. + } catch (e: unknown) { + if (e instanceof Error && (e.name === 'CanceledError' || (e as any).code === 'ERR_CANCELED')) return []; setError('Could not load programs. Please refresh.'); return []; } @@ -162,6 +162,10 @@ export function ProgramProvider({ children }: ProgramProviderProps): React.React if (stored) { const storedMatch = programs.find((p) => p._id === stored); if (storedMatch) return storedMatch; + try { + window.localStorage.removeItem(STORAGE_KEY_NEW); + window.localStorage.removeItem(STORAGE_KEY_OLD); + } catch { /* ignore */ } } // 4. Server-reported isDefault (admin-promoted default program) diff --git a/apps/frontend/src/pages/HomePage.tsx b/apps/frontend/src/pages/HomePage.tsx index 85c2d262..bbfce572 100644 --- a/apps/frontend/src/pages/HomePage.tsx +++ b/apps/frontend/src/pages/HomePage.tsx @@ -337,7 +337,7 @@ function NumberedSkeletonRow({ rank }: { rank: number }): React.ReactElement { // Main page // ═══════════════════════════════════════════════════════════════════════════ export default function HomePage() { - const { currentBatch } = useBatch(); + const { currentBatch, loading: programLoading } = useBatch(); const batchId = currentBatch?._id ?? null; // ── Core data ──────────────────────────────────────────────────────────── @@ -388,6 +388,13 @@ export default function HomePage() { }); }, []); + // ── Redirect if no program is selected ─────────────────────────────────── + useEffect(() => { + if (!programLoading && !batchId) { + navigate('/programs', { replace: true }); + } + }, [programLoading, batchId, navigate]); + // ── Fetch all data sources dynamically when batchId changes ────────────── useEffect(() => { if (!batchId) return; diff --git a/apps/frontend/src/pages/ProgramPage.tsx b/apps/frontend/src/pages/ProgramPage.tsx index d41effaf..14a7f40e 100644 --- a/apps/frontend/src/pages/ProgramPage.tsx +++ b/apps/frontend/src/pages/ProgramPage.tsx @@ -64,7 +64,7 @@ export default function ProgramPage() { setData(res.data); setCurrentBatch(res.data.program._id); } catch (e: unknown) { - if (e instanceof Error && e.name === 'CanceledError') return; + if (e instanceof Error && (e.name === 'CanceledError' || (e as any).code === 'ERR_CANCELED')) return; setError('This program is unavailable or has been archived.'); } finally { setLoading(false); @@ -101,7 +101,7 @@ export default function ProgramPage() { for (const k of sorted) ordered[k] = next[k]; setGrouped(ordered); } catch (e: unknown) { - if (e instanceof Error && e.name === 'CanceledError') return; + if (e instanceof Error && (e.name === 'CanceledError' || (e as any).code === 'ERR_CANCELED')) return; } finally { setFaqsLoading(false); } diff --git a/apps/frontend/src/pages/ProgramPortalPage.tsx b/apps/frontend/src/pages/ProgramPortalPage.tsx index 68943ca9..f1185e67 100644 --- a/apps/frontend/src/pages/ProgramPortalPage.tsx +++ b/apps/frontend/src/pages/ProgramPortalPage.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import { motion } from 'framer-motion'; +import { motion, AnimatePresence } from 'framer-motion'; import Footer from '../components/layout/Footer'; import { HomeDoodles } from '../components/ui/PageDoodles'; import api from '../utils/api'; @@ -15,8 +15,6 @@ interface PublicBatch { startDate: string; endDate: string; isActive: boolean; - /** v1.69 β€” true for the single program the admin has flagged as - * the one to show to non-admin visitors on the home page. */ isDefault?: boolean; faqCount: number; } @@ -26,50 +24,51 @@ interface BatchesResponse { } /** - * v1.69 β€” Program portal. The home page `/` is now the entry point to - * every program run. It lists active programs as cards and lets an - * admin create a new one. Clicking a card sets that program as the - * active one in `BatchContext` and routes to `/program/:slug`. - * - * Replaces the previous HomePage that rendered a single batch's FAQ - * feed. The FAQ feed still exists; it now lives at `/program/:slug`. + * v1.69 β€” Program portal. Lists active programs and lets admins create new ones. */ export default function ProgramPortalPage() { const { user, isAuthenticated } = useAuth(); - const { setCurrentBatch } = useBatch(); + const { setCurrentBatch, refresh: refreshContext } = useBatch(); const navigate = useNavigate(); const [batches, setBatches] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + // ── Create program modal state ──────────────────────────────────────────── + const [showCreate, setShowCreate] = useState(false); + const [creating, setCreating] = useState(false); + const [createError, setCreateError] = useState(null); + const today = new Date().toISOString().split('T')[0]; + const sixMonthsLater = new Date(Date.now() + 180 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; + const [form, setForm] = useState({ + name: '', + description: '', + startDate: today, + endDate: sixMonthsLater, + }); + + const fetchBatches = useCallback(async (signal?: AbortSignal) => { + try { + const res = await api.get('/batches', { signal }); + setBatches(res.data.batches ?? []); + setError(null); + } catch (e: unknown) { + if (e instanceof Error && (e.name === 'CanceledError' || (e as any).code === 'ERR_CANCELED')) return; + setError('Could not load programs. Please refresh.'); + } finally { + setLoading(false); + } + }, []); + useEffect(() => { const controller = new AbortController(); - (async () => { - try { - const res = await api.get('/batches', { signal: controller.signal }); - setBatches(res.data.batches ?? []); - } catch (e: unknown) { - if (e instanceof Error && e.name === 'CanceledError') return; - setError('Could not load programs. Please refresh.'); - } finally { - setLoading(false); - } - })(); + fetchBatches(controller.signal); return () => controller.abort(); - }, []); + }, [fetchBatches]); const isAdmin = user?.role === 'admin' || user?.role === 'moderator'; - /** - * v1.69 β€” visibility model: - * - Admins see every active program (they need to manage all of them). - * - Non-admins see ONLY the program flagged `isDefault: true`. That's - * the single program the admin has "selected" via the "Set as - * default" action on /admin/batches. Until an admin picks one, - * visitors see every active program (the legacy portal behaviour) - * so the page never appears empty by accident. - */ const visibleBatches = useMemo(() => { if (isAdmin) return batches; const defaultB = batches.find((b) => b.isDefault); @@ -78,7 +77,6 @@ export default function ProgramPortalPage() { const sortedBatches = useMemo(() => { return [...visibleBatches].sort((a, b) => { - // Live programs first (start ≀ now ≀ end), then upcoming, then ended const now = Date.now(); const aLive = new Date(a.startDate).getTime() <= now && now <= new Date(a.endDate).getTime(); const bLive = new Date(b.startDate).getTime() <= now && now <= new Date(b.endDate).getTime(); @@ -93,6 +91,32 @@ export default function ProgramPortalPage() { navigate(`/program/${slug}`); }; + const handleCreate = async (e: React.FormEvent) => { + e.preventDefault(); + setCreating(true); + setCreateError(null); + try { + await api.post('/batches', { + name: form.name, + description: form.description, + startDate: form.startDate, + endDate: form.endDate, + isActive: true, + }); + // Refresh both local list and the global ProgramContext + setLoading(true); + await fetchBatches(); + await refreshContext(); + setShowCreate(false); + setForm({ name: '', description: '', startDate: today, endDate: sixMonthsLater }); + } catch (err: unknown) { + const msg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message; + setCreateError(msg || 'Failed to create program. Please try again.'); + } finally { + setCreating(false); + } + }; + return (
@@ -129,15 +153,28 @@ export default function ProgramPortalPage() { className="text-lg text-ink-soft max-w-2xl" > {isAdmin - ? 'Every FAQ, community thread, and Zoom transcript is scoped to a single program run. Pick the one you\'re working on, or create a new one.' + ? "Every FAQ, community thread, and Zoom transcript is scoped to a single program run. Pick the one you're working on, or create a new one." : sortedBatches.length === 1 ? 'This is the current program. Click below to enter.' : 'Pick a program run to enter.'} - {/* Programs are created and managed exclusively from - /admin/batches. The portal at /programs is read-only β€” - for switching between existing programs only. */} + {/* Admin: Create program button */} + {isAdmin && ( + setShowCreate(true)} + id="create-program-btn" + className="mt-6 inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-accent text-bg font-semibold text-sm hover:bg-accent/90 transition-all duration-150 shadow-md hover:shadow-lg" + > + + + + Create New Program + + )}
{error && ( @@ -159,14 +196,26 @@ export default function ProgramPortalPage() { {!loading && !error && sortedBatches.length === 0 && (
+
πŸ—‚οΈ

No programs yet.

-

+

{isAdmin - ? 'Create the first program from the admin panel to get started.' + ? "Create the first program to get started. Everything β€” FAQs, community posts, and Zoom transcripts β€” will be scoped to it." : 'Programs appear here once an admin creates them.'}

+ {isAdmin && ( + + )}
)} @@ -259,6 +308,134 @@ export default function ProgramPortalPage() {