Skip to content
lyc280705Public

About

An intelligent university Q&A system based on LangGraph and React.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

FZU-Chat

简体中文

A Fuzhou University intelligent Q&A system with student authentication and educational system integration, built with LangGraph, FastAPI, and React.

Python React FastAPI Docker

Current tagged release: v7.30

Release notes: CHANGELOG.md

Overview

FZU-Chat provides a ChatGPT-style conversation experience for Fuzhou University students. Each student logs in with their student ID to get isolated conversations and access to educational system tools (grade queries, course schedules, etc.); external visitors can use third-party guest login for public Q&A and campus-life guidance.

Features

  • Student authentication: Per-student login with conversation isolation
  • Independent passkey login: Create a discoverable WebAuthn passkey and enter as a new visitor, without a username, email, password or third-party account. Existing users can also add passkeys to their current identity.
  • Third-party guest mode: WeChat, QQ, Alipay, Microsoft, Apple, and GitHub OAuth can create visitor sessions with public Q&A, knowledge retrieval, web search, and campus-life recommendations, without binding personal grade, schedule, selection, or academic-affairs tools
  • Educational system tools: Query grades, courses, exam scores, and student info via the FZU academic affairs system (based on west2-online/jwch)
  • Educational session cleanup: The app only keeps educational-system session cookies on the server side and never stores the raw password; logging out clears both the site login state and the cached educational-session cookies
  • Professional privacy controls: Versioned privacy terms, account data statistics, saved-data reset, and irreversible account deletion that removes service data, revokes every active session, clears local preferences, and signs out
  • ChatGPT-style interface: Modern dark UI with sidebar history, quick actions, and streaming replies
  • Message editing and regeneration: Icon-only actions for copying replies, regenerating an assistant answer, or editing a sent user message and rebuilding the following response branch
  • Accessible interaction polish: Keyboard-friendly focus rings, skip link, screen-reader status updates, dialog focus handling, and live chat-log announcements
  • Low-intrusion campus intelligence: After login or educational reconnect, the backend refreshes cached course, exam, selection, and grade-summary snapshots; each new conversation freezes one compact runtime context so the model can decide whether a gentle end-of-answer reminder is useful without changing old messages
  • Rich tool cards: Visual display of tool calls with structured data tables for grades and courses
  • Multi-model support: Huawei Cloud MaaS GLM-5.3, Kimi K2.6, and DeepSeek V4.1 Flash selection, with qwen3-30b-a3b for title summarization
  • FZU-aware personalized memory: Confirmed long-term preferences for names, answer style, course-selection habits, academic-query presentation, campus-life needs, and dining/campus preferences, while volatile educational facts remain live tool queries
  • Knowledge + web search: FAISS retrieval plus Bocha web search fallback
  • Launch-ready single-node hardening: Strict SPA fallback, scanner-path 404s, Redis-backed sessions/rate limits, readiness and Prometheus-style metrics
  • Docker deployment: Multi-stage build for React frontend + Python backend, plus production Compose with internal Redis

Project Structure

fzu-chat/
├── app/
│   ├── server.py          # FastAPI backend with auth + per-user conversations
│   ├── graph.py           # LangGraph workflow with edu tools
│   ├── auth.py            # Token-based authentication & session management
│   ├── oauth.py           # Third-party visitor OAuth helpers
│   ├── jwch_client.py     # FZU undergraduate system client (Python port)
│   ├── edu_tools.py       # LangGraph tools for educational queries
│   ├── campus_recommendations.py # Contextual dining/study recommendation service
│   ├── campus_dynamic_context.py # Low-latency dynamic context and reminder suppression
│   ├── user_memory_tools.py # Confirmed personalized-memory tools
│   ├── memory_store.py    # SQLite-backed long-term memory store
│   ├── data/              # Knowledge base documents
│   ├── faiss/             # FAISS vector database
│   ├── png/               # Static assets
│   └── storage/           # Per-user conversation storage
├── frontend/
│   ├── src/App.jsx        # React chat UI with login
│   ├── src/App.css        # Modern dark theme styles
│   └── vite.config.js     # Vite config with API proxy
├── Dockerfile
├── docker-compose.prod.yml
├── scripts/              # Deploy, rollback, and SQLite backup helpers
├── docker-compose.yml
└── requirements.txt

Required API Keys

  • HUAWEICLOUD_MAAS_API_KEY – Huawei Cloud MaaS OpenAI-compatible API for GLM-5.3, Kimi K2.6, DeepSeek V4.1 Flash, and qwen3-30b-a3b title summarization
  • DASHSCOPE_API_KEY – Alibaba Cloud DashScope embeddings for the local knowledge base
  • BOCHA_API_KEY – Bocha web search
  • LANGSMITH_API_KEY – LangSmith tracing
  • AMAP_WEB_SERVICE_KEY – Optional AMap Web Service key for reverse geocoding browser location into text, POI supplementation, and walking/bicycling route distance; local development can also use amap_web_service_key.txt or AMAP_WEB_SERVICE_KEY_FILE; requests are throttled to at most 5 QPS by default; reverse-geocoding uses a short timeout and 10-minute in-process cache; when unset, campus recommendations fall back to the built-in FZU place library and estimated distance
  • FZU_CHAT_OAUTH_PROVIDERS – Optional comma-separated visible visitor-login provider allowlist, e.g. microsoft,apple,github for production while keeping WeChat and QQ available in the codebase
  • FZU_CHAT_WECHAT_CLIENT_ID / FZU_CHAT_WECHAT_CLIENT_SECRET, FZU_CHAT_QQ_CLIENT_ID / FZU_CHAT_QQ_CLIENT_SECRET, FZU_CHAT_MICROSOFT_CLIENT_ID / FZU_CHAT_MICROSOFT_CLIENT_SECRET, FZU_CHAT_APPLE_CLIENT_ID / FZU_CHAT_APPLE_CLIENT_SECRET, FZU_CHAT_GITHUB_CLIENT_ID / FZU_CHAT_GITHUB_CLIENT_SECRET – Optional visitor OAuth credentials; the matching *_REDIRECT_URI variables can override the default /api/auth/oauth/{provider}/callback callback URL. Apple uses the generated client-secret JWT for FZU_CHAT_APPLE_CLIENT_SECRET; Microsoft can also set FZU_CHAT_MICROSOFT_TENANT, defaulting to common.

For local development, the backend also reads root-level key files such as huaweicloud_maas_api_key.txt, dashscope_api_key.txt, and bocha_api_key.txt when container secrets and environment variables are not set.

Local Development

# 1. Install backend dependencies
pip install -r requirements.txt

# 2. Set environment variables
export HUAWEICLOUD_MAAS_API_KEY=...
export DASHSCOPE_API_KEY=...
export BOCHA_API_KEY=...
export LANGSMITH_API_KEY=...
export AMAP_WEB_SERVICE_KEY=... # optional, for text location and campus routes
export FZU_CHAT_OAUTH_PROVIDERS=microsoft,apple,github # optional visible provider allowlist
export FZU_CHAT_GITHUB_CLIENT_ID=... # optional, for visitor GitHub login
export FZU_CHAT_GITHUB_CLIENT_SECRET=...

# 3. Start backend
uvicorn app.server:app --host 0.0.0.0 --port 8000

# 4. Start frontend dev server
cd frontend && npm ci && npm run dev

# 5. Open http://localhost:5173

Docker Deployment

# 1. Create API key files
echo "your-key" > huaweicloud_maas_api_key.txt
echo "your-key" > dashscope_api_key.txt
echo "your-key" > bocha_api_key.txt
echo "your-key" > langsmith_api_key.txt
echo "your-key" > amap_web_service_key.txt # optional, for contextual recommendations

# 2. Build and run local Compose
docker compose up -d --build

# 3. Visit http://localhost:80

Production deployment can use docker-compose.prod.yml with an internal Redis container. Set a URL-safe REDIS_PASSWORD such as openssl rand -hex 32, provision the session encryption key below, then run FZU_CHAT_VERSION=v7.27 ./scripts/deploy-ghcr.sh; if GHCR image pull fails, the script falls back to a local production image build. Hosts using additional Compose files (resource limits or Alipay secrets) must include all override files in their deployment commands rather than using this single-file helper.

Frontend builds use npm ci to install the committed lockfile and run npm run audit:security before producing the Docker image. Known npm advisories at low severity or above block the build; resolve them by updating compatible dependencies rather than disabling the check. This check covers npm dependencies, not a full Python or operating-system security audit.

Before starting either Docker Compose configuration, create the persistent session encryption key once:

python scripts/generate-session-key.py

The script creates session_encryption_key.txt with owner-only permissions and refuses to overwrite an existing key. Compose mounts it read-only as a secret; it is excluded from Git. Keep this file across releases and protect its backup separately from Redis. Do not regenerate it during ordinary deployments: losing or replacing it invalidates encrypted sessions.

Redis session values (including education cookies) use authenticated encryption, and website bearer tokens are hashed before use as Redis keys. Startup migrates existing plaintext sessions without changing browser tokens or extending their lifetime. A missing/invalid key blocks startup rather than silently storing plaintext. Rolling back to a release predating encrypted sessions requires users to sign in again; conversation data is unaffected. Encryption does not protect against an attacker who controls the application server and can also read its key.

Education connections are shared across authenticated sessions for the same education account. Undergraduate and graduate identities have separate storage and credentials. A revision check prevents stale requests from invalidating a newly connected session. The device event stream sends only connection status, never credentials; normal logout affects that device only, while account deletion revokes all devices and removes shared credentials. Browser focus/online events and periodic refresh reconcile missed status updates.

Graduate login follows west2-online/yjsy v0.0.11 and exposes grades, semester schedules, student profiles and exam rooms. Unsupported personal tools are not bound to graduate sessions. Login and reconnect share a conservative five-attempt budget per account per 30 minutes and never retry automatically. The login-page selector retains the existing layout. Validation uses synthetic upstream responses; authenticated graduate login and real page parsing still require a student-account check. See the protocol mapping and verification boundaries.

Teaching-system and CAPTCHA requests validate TLS certificates. A private trusted CA bundle can be configured through FZU_CHAT_JWCH_CA_BUNDLE for the teaching-system client; disabling certificate verification is not supported.

Useful production environment variables:

  • FZU_CHAT_REDIS_URL – Redis URL for session, rate-limit, stream-slot, and background-task dedupe state
  • FZU_CHAT_PUBLIC_DOCS=false – keep /openapi.json, Swagger, and ReDoc private by default
  • FZU_CHAT_GLOBAL_STREAM_LIMIT=80, FZU_CHAT_USER_STREAM_LIMIT=5 – active SSE generation caps for initial campus-scale launch
  • FZU_CHAT_STATIC_FALLBACK=strict – return the SPA only for / and index.html
  • FZU_CHAT_METRICS_ENABLED=true – enable restricted /api/metrics
  • FZU_CHAT_OAUTH_PROVIDERS=microsoft,apple,github – show only Microsoft, Apple, and GitHub in production; omit it to show all repository-supported providers
  • FZU_CHAT_WECHAT_CLIENT_ID / FZU_CHAT_WECHAT_CLIENT_SECRET, FZU_CHAT_QQ_CLIENT_ID / FZU_CHAT_QQ_CLIENT_SECRET, FZU_CHAT_MICROSOFT_CLIENT_ID / FZU_CHAT_MICROSOFT_CLIENT_SECRET, FZU_CHAT_APPLE_CLIENT_ID / FZU_CHAT_APPLE_CLIENT_SECRET, FZU_CHAT_GITHUB_CLIENT_ID / FZU_CHAT_GITHUB_CLIENT_SECRET – enable the matching visitor login entry; unconfigured providers are shown as unavailable in the login UI

API Endpoints

Alipay login deployment

Alipay is optional and disabled until the application has passed platform review. Configure RSA2 (2048-bit or stronger) on a web application with member-information authorization, and register https://mylingxi.cn/api/auth/oauth/alipay/callback using full-address matching. Official OAuth guide and official Python SDK protocol reference.

  • Keep the PKCS8/PKCS1 PEM application private key outside the repository and Docker build context, with owner-only permissions. Upload only its matching public key to Alipay.
  • Download the distinct Alipay public key from the platform for response verification (base64 DER or PEM). Do not substitute the application public key.
  • Set FZU_CHAT_ALIPAY_APP_ID, FZU_CHAT_ALIPAY_REDIRECT_URI, FZU_CHAT_ALIPAY_PRIVATE_KEY_HOST_FILE, and FZU_CHAT_ALIPAY_PUBLIC_KEY_HOST_FILE in the host .env and add alipay to FZU_CHAT_OAUTH_PROVIDERS.
  • Include docker-compose.alipay.yml alongside production/resource-limit Compose files. It mounts both key files as read-only secrets. No new Python dependency is required.
  • Keep FZU_CHAT_ALIPAY_ENABLED=false until review is approved. Configured but pending providers render as a disabled “待上线” entry. After approval, set it to true and recreate only the app container with all overrides.
docker compose -f docker-compose.prod.yml -f docker-compose.2g.override.yml -f docker-compose.alipay.yml pull --policy always fzu-chat
docker compose -f docker-compose.prod.yml -f docker-compose.2g.override.yml -f docker-compose.alipay.yml up -d --no-deps fzu-chat

The callback accepts auth_code, checks browser-bound state, atomically consumes it in Redis, exchanges the code through a signed RSA2 request, and verifies the exact UTF-8 response before trusting identity. New applications use open_id; legacy user_id is supported only when open_id is absent. Profile identity must match the token response. Only hashed identity, nickname and HTTPS avatar are retained; access/refresh tokens, phone numbers and real-name fields are not stored. No payment or transfer APIs are implemented.

Uvicorn access logs strip OAuth query parameters. For Nginx, install deploy/nginx-oauth-logging.conf in its HTTP context and use access_log /var/log/nginx/fzu-chat.access.log fzu_privacy; in the site's server blocks. This retains request paths/status/timing without logging authorization codes, state or referrer URLs. Existing historical logs are not altered. Verify nginx -t before reloading. The Alipay glyph uses Simple Icons (CC0); the Alipay trademark belongs to its respective owner.

Mobile browsers outside Alipay

Mobile Safari/Chrome/Edge cannot directly display Alipay's H5 authorization page. The current login button opens a small same-origin entry inside Alipay, then starts Alipay's official web authorization. The entry checks the existing legal-consent marker before navigation. The server binds a one-time OAuth state to an HttpOnly cookie in that Alipay client and verifies the provider's signed response before creating a session. No native JavaScript bridge is required. On success the user continues chatting inside Alipay. The original Edge/Safari tab is not logged in; no browser-return button, polling or clipboard is required. Failures return to the regular login page with a normal error message, never a debug panel or an automatic retry loop.

An explicit “打开支付宝” link remains if automatic launching is blocked. Users who prefer their normal browser can choose Passkey or another supported login. Alipay may still display its own external-site warnings; our code cannot suppress them. App launch, cookie persistence and authorization must still be accepted on real devices, separately from automated signed-callback tests.

Experimental native/handoff endpoints, return-browser pages and temporary diagnostics have been removed. No session credential is placed in a URL, and the strict same-origin script policy is retained. The v7.30 image includes the tested server hotfix; retire docker-compose.hotfix.yml when upgrading from v7.29, while keeping the production, resource-limit and Alipay-secret Compose files.

Passkey login

  • The login page offers 通行密钥 · Passkey alongside OAuth. First-time users choose “创建通行密钥并进入”; a random visitor identity is created only after successful WebAuthn verification. No username, email or prior account is needed.
  • Returning users choose an existing discoverable passkey. Creating again while logged out creates another identity, not a recovery or automatic account merge. Public Q&A is available, but independent visitors have no academic-affairs access.
  • Account settings allow adding/removing keys for the current identity after a login within the last ten minutes. Maximum ten keys; the last key of a passkey-only identity cannot be removed except by deleting that identity.
  • Keep a synced passkey or enroll an additional device. Losing all usable keys with no other login method means the identity cannot be recovered. In-app browsers may not support WebAuthn; use a supported secure system browser.
  • FZU_CHAT_WEBAUTHN_ORIGIN defaults to https://mylingxi.cn; RP ID is derived from this trusted configuration, not client headers. Keep it stable between releases. Custom deployments must set their canonical HTTPS origin and preserve that public origin through their reverse proxy. Passkeys are domain-bound.
  • app/storage/passkeys.sqlite persists public keys, random handles, profile metadata and short-lived challenges. Preserve the storage volume and include it in encrypted SQLite backups (the existing wildcard backup script covers it). No private keys, biometrics or device-unlock secrets reach the server.
  • Verification requires the exact origin/RP, signature, user verification and cookie-bound one-use challenge. Enrollment is additionally bound to the recent session; key changes and account deletion are serialized. Requests are subject to same-origin JSON/custom-header checks and rate limits. Deleting the account removes server-side keys and revokes all sessions, but not the device-manager entry.

Authentication

  • POST /api/auth/login – Login with student ID + password
  • GET /api/auth/oauth/providers – Report visible visitor-login availability
  • GET /api/auth/oauth/{provider}/start – Start a visible visitor OAuth redirect
  • GET|POST /api/auth/oauth/{provider}/callback – OAuth callback that creates a visitor session without educational tools; POST is used for Apple form_post
  • POST /api/auth/logout – Logout and clear both the site login state and the server-side educational-session cookies
  • GET /api/auth/me – Current user info
  • GET /api/user-data – Current account conversation, message, and confirmed-memory counts
  • DELETE /api/user-data – Clear saved service data while keeping the account signed in
  • DELETE /api/account – Irreversibly delete service-side account data, revoke all sessions, and sign out

Chat

  • GET /api/models – Available chat models
  • GET /api/health – Lightweight liveness check
  • GET /api/ready – Readiness check for Redis, SQLite writeability, and configured runtime limits
  • GET /api/metrics – Restricted Prometheus-style operational metrics
  • GET /api/conversations?limit=50&cursor=... – Paginated conversation list with preview and message counts
  • POST /api/conversations – Create new conversation
  • GET /api/conversations/{id} – Conversation detail
  • DELETE /api/conversations/{id} – Delete conversation
  • POST /api/conversations/{id}/messages – Stream assistant response (SSE)
  • POST /api/conversations/{id}/messages with rerun_message_id – Edit or regenerate from an existing user message while preserving the SSE event format
  • POST /api/conversations/{id}/messages may include context.location – One-message transient location context for dynamic reminders and campus recommendations, never persisted to chat history
  • POST /api/conversations/{id}/feedback – Save feedback
  • POST /api/conversations/{id}/memory-proposals/{tool_id} – Confirm or dismiss a memory save/delete proposal

Contextual Recommendations

  • GET /api/recommendations/locations – Built-in manual campus/location options
  • POST /api/recommendations/signal-refresh – Asynchronously refresh non-sensitive academic summary snapshots used by low-intrusion reminders
  • POST /api/recommendations/contextual – Generate one-time campus recommendations from scenario, optional browser location, manual_location_id, and optional seen_grade_digest

Low-intrusion reminders no longer render automatic homepage cards and do not synchronously fetch slow educational-system data when a user sends a message. The backend reads cached non-sensitive summaries plus reminder cooldown state, injects a few dynamic events into a second short SystemMessage, and stores that runtime context once per conversation so later turns append messages without rewriting prior prompt content. Conversation history uses LangChain's approximate token counter and is only trimmed around the 200k-token boundary, preserving tool results for long chats. Browser coordinates are transient per message: they are used server-side for campus recommendations and AMap reverse geocoding, while only the resulting text location can be injected into the current model prompt. Coordinates and text locations are not persisted to conversation storage or long-term memory. Grade summaries store only digests, term labels, and recorded counts, never concrete scores. Mobile geolocation requires an HTTPS origin; plain HTTP server URLs will not show the browser permission prompt. The AMap key stays server-side through environment variables or Docker secrets.

Educational Tools (via Agent)

The LLM agent can automatically call these tools only in undergraduate educational-login sessions; visitor mode never binds these personal academic-affairs tools:

  • query_grades – Course grades and GPA
  • query_courses – Course schedule
  • query_student_info – Student profile
  • query_exam_scores – CET and unified exam scores
  • recommend_campus_context – Contextual campus academic, dining, and study recommendations when the user supplies or authorizes a location

Validation

# Frontend
cd frontend && npm run lint && npm run build

# Backend
python -m compileall app

About

An intelligent university Q&A system based on LangGraph and React.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages