Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
An immersive AI-powered tourism guide for Asturias, Spain. Explore the region through visual stories, curated itineraries, and voice chat with a local AI guide. Built with Next.js, Supabase, and ElevenLabs conversational AI.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Paisaxe - Look. Ask. Discover.
An AI-powered tourism experience for Asturias, Spain. Explore the region through immersive visual stories and ask questions via voice or text to discover landscapes, local gastronomy, hiking routes, and more.
"Paisaxe" means "landscape" in Asturian (Bable), the traditional language of the region.README 전문 보기 ↗An AI-powered tourism experience for Asturias, Spain. Explore the region through immersive visual stories and ask questions via voice or text to discover landscapes, local gastronomy, hiking routes, and more.
"Paisaxe" means "landscape" in Asturian (Bable), the traditional language of the region.
During the PayPal AI Hackathon, Paisaxe gained a booking flow for local experiences. Visitors describe in the text chat how many people are going, the date, their budget and any needs (for example, step-free access). The assistant compares that with the provider's catalog and its stated facts. Then it makes an explicit offer, holds a place when the visitor accepts, and takes a deposit through PayPal (sandbox). Visitors can see and cancel the booking afterwards. The decision is recorded in ADR-0024, and the implementation follows the plan and its notes (deviations, reviews and evidence for each phase).
POST /api/booking/chat/stream runs Claude with tools:
search_experiences, update_booking_draft, get_quote, create_payment_order,
get_booking_status and preview_cancellation. Tools take ids, not amounts. Every
price comes from the database, inputs are validated with zod, and ownership is checked
on the server. A visitor need is judged supported, unsupported or unknown from the
provider's facts. The assistant may not claim that an unknown is suitable.@paypal/paypal-server-sdk 2.5.0, built with the PayPal Context Plugin; see
docs/hackathon/apimatic.md) with an idempotency key,
and the buyer approves by redirect. A single capture path is shared by the return page,
the CHECKOUT.ORDER.APPROVED webhook and a reconciliation cron that runs every 5 minutes,
so a buyer who approves and closes the browser is still confirmed. Webhooks are
signature-verified and stored in an inbox, so a failed event is retried. A capture that
cannot be honoured is refunded exactly once./booking/<id>.<token>, an HMAC that can be
rebuilt and revoked. The link alone opens the booking: status, payment, cancellation.
Tokens never reach the model and are redacted from PostHog, Sentry and Vercel Analytics./acceso redeems a
voucher code with a Supabase anonymous session, so no account is needed. Every
booking-start route answers 404 without an active redemption, while the
experience_booking flag is off, or on a Vercel Preview. Each redemption is metered
(60 chat turns and 10 booking attempts by default). A voucher may also grant a 24-hour
pass for the existing voice guide.The catalog belongs to a fictitious merchant, "Rutas del Sella (demo, ficticio)"
(merchants.is_fixture = true). It is seeded by migration
116_operator_access_and_fixture.sql with three experiences, available every day at 10:00
and 16:00 (Madrid time), 12 places per slot, all prices for one party:
| Experience | Price | Deposit | Max party | Step-free access (provider fact) |
|---|---|---|---|---|
| Paseo por la senda costera (coastal walk) | €120 | €30 | 6 | yes |
| Descenso en canoa (canoe descent) | €60 | €15 | 4 | no |
| Ruta de miradores en 4x4 (4x4 viewpoint route) | €200 | €50 | 6 | unknown (not confirmed) |
Every booking card carries a "Demo" badge, and the payment card says "PayPal sandbox: test payment, no real money". The operator view marks the merchant "demo". No real provider takes part, and none is implied.
Everything below runs on your machine against a local Docker Supabase. Never point these steps at a production project.
Required: Node.js 24, npm, Docker. Optional: an Anthropic API key (needed only for the chat itself; the Postman run below does not need it), a Voyage AI key (guide context in the chat; skipped without one), and your own PayPal sandbox app (instead of the local PayPal mock).
Install and start the local database:
git clone https://github.com/juan294/paisaxe.git && cd paisaxe
npm install
npx supabase start -x vector,logflare # the vector container is unhealthy on some machines
npx supabase db reset # re-applies every migration, including 112-124
Migrations 112 to 124 create the booking schema, the fixture merchant and the operator
row. They also turn the experience_booking flag on for the development environment
(it is off for production). Local anonymous sign-ins are on in supabase/config.toml.
Environment variables. The template is .env.example. For the booking flow the app
needs:
| Variable | Local value |
|---|---|
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_KEY | API_URL, ANON_KEY and SERVICE_ROLE_KEY from npx supabase status -o env |
BOOKING_LINK_SECRET | any random value of at least 32 bytes, e.g. openssl rand -base64 48 |
PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_API_BASE | the mock: local-mock, local-mock, http://127.0.0.1:4010. Your sandbox app: its credentials and https://api-m.sandbox.paypal.com (the only non-loopback host the adapter accepts) |
PAYPAL_WEBHOOK_ID | optional; the id of a sandbox webhook that reaches your machine through a tunnel. Without it, the return page and reconciliation still confirm payments |
ANTHROPIC_API_KEY | optional; needed for the booking chat |
Reproduce the whole journey with the Postman collection, Newman and the PayPal mock. In three terminals, follow docs/hackathon/postman/README.md:
npx tsx scripts/booking/postman-local.ts # 1: PayPal mock, flag on, a local voucher
# 2: next dev on port 3006 with only the local variables (exact command in that README)
npx --yes newman@6 run docs/hackathon/postman/paisaxe-booking.postman_collection.json \
-e "$TMPDIR/paisaxe-postman/paisaxe-booking.local.postman_environment.json" # 3
next dev rewrites AGENTS.md; restore it with git checkout -- AGENTS.md.
The baseline is commit d368b2b3 (1 October 2026). git log d368b2b3.. lists every
change since then. The booking feature is the bulk of it:
accept_quote, availability with one inventory owner, provider facts, the fixture
merchant and the flag./acceso, redeem_voucher), anonymous guest sessions,
metering and Preview isolation.?booking=1 text
entry.LICENSE file, the review-workflow author check and the
release probes.The same range also contains unrelated maintenance: dependency updates, a database security fix (migration 110), the RAG embedding-model fix and migration 111, CI pinning, and the removal of the tourism guide PDFs from the repository.
https://*.supabase.co, so a browser cannot reach
http://127.0.0.1:54321. Locally, the journey is reproduced over HTTP (Postman/Newman),
by the test suites, and by the booking-roundtrip Playwright probe, which runs it in a
browser with the CSP bypassed for that test only.--no-voice).PAYMENT.CAPTURE.PENDING and PAYMENT.CAPTURE.DENIED are handled
but were not observed in the sandbox. Events that match no payment (for example, from
another environment sharing the sandbox app) stay in the inbox and are not replayed.
Reconciliation queries every open payment directly, so no payment depends on them.| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS 4 + shadcn/ui |
| Database | Supabase (PostgreSQL + pgvector) |
| AI Chat | Claude API (Anthropic) |
| Embeddings | Voyage AI (voyage-3.5, 512 dims) |
| Reranking | Voyage AI (rerank-2.5) |
| Voice Agents | ElevenLabs Conversational AI |
| Auth | Google OAuth via Supabase Auth |
| Testing | Vitest + React Testing Library + Playwright |
| Deployment | Vercel |
| Monitoring | Upptime + Vercel Speed Insights |
| Logging | Pino (structured JSON) + Sentry (error tracking) |
| Cache | Upstash Redis (embedding cache, rate limiting) |
Clone the repository:
git clone https://github.com/juan294/paisaxe.git
cd paisaxe
Install dependencies:
npm install
Copy the environment template:
cp .env.example .env.local
Configure your environment variables in .env.local:
# AI Services
ANTHROPIC_API_KEY=
VOYAGE_API_KEY=
ELEVENLABS_API_KEY= # Voice agents (optional)
# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_KEY=
# Admin
ADMIN_SECRET_KEY=
# Google OAuth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Webhooks
WEBHOOK_SECRET=
# Site URL
NEXT_PUBLIC_SITE_URL=http://localhost:3000
Set up the database:
supabase/migrations/ (96 migration files)Seed the database with tourism content:
npm run process-pdfs # Extract content from PDFs
npm run extract-images # Extract images from PDFs
npm run seed-all # Seed images + embeddings
Start the development server:
npm run dev
Open http://localhost:3000 in your browser.
| Command | Description |
|---|---|
npm run dev | Start development server |
npm run build | Create production build |
npm run build:analyze | Create a webpack analyzer build and write bundle reports under .next/analyze/ |
npm run start | Start production server |
npm run lint | Run ESLint for src/ and TypeScript scripts |
npm run lint:scripts | Run ESLint for scripts/ |
npm run typecheck | Run TypeScript checks for app, scripts, E2E, and Edge functions |
npm run check-verification-coverage | Verify CI/package wiring for non-src and live-gate coverage |
| Command | Description |
|---|---|
npm run test | Run unit & component tests (Vitest) |
npm run test:watch | Watch mode |
npm run test:coverage | Generate coverage report |
npm run test:ui | Open Vitest UI |
npm run test:e2e | Run E2E tests (Playwright, headless) |
npm run test:e2e:ui | Playwright UI mode |
npm run test:e2e:headed | Run with visible browser |
npm run test:e2e:debug | Debug mode with inspector |
npm run test:e2e:stripe | Run real Stripe test-mode checkout integration (requires Stripe/Supabase QA env vars) |
npm run prelaunch:live | Run launch-critical live integration gate; fails instead of skipping missing credentials |
| Command | Description |
|---|---|
npm run process-pdfs | Extract text content from PDFs |
npm run extract-images | Extract images from PDFs |
npm run generate-stories | Generate story definitions |
npm run seed-db | Generate embeddings and populate DB |
npm run seed-db:clear | Clear and re-seed embeddings |
npm run seed-images | Seed extracted images to DB |
npm run seed-images:clear | Clear and re-seed images |
npm run seed-all | Seed images + embeddings |
npm run seed-all:clear | Clear and re-seed everything |
paisaxe/
├── e2e/ # Playwright E2E tests
│ ├── fixtures/ # Mock data for E2E
│ └── *.spec.ts # Test files
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── page.tsx # Redirects to /immersive
│ │ ├── layout.tsx # Root layout
│ │ ├── admin/ # Admin panel
│ │ ├── auth/ # OAuth callback
│ │ ├── favorites/ # User favorites page
│ │ ├── immersive/ # Immersive stories page
│ │ ├── story/ # Individual story pages
│ │ └── api/
│ │ ├── chat/ # Chat endpoint
│ │ ├── health/ # Health check (uptime monitoring)
│ │ └── webhooks/ # Supabase webhook receiver
│ ├── components/
│ │ ├── a11y/ # Accessibility components
│ │ ├── admin/ # Admin panel components
│ │ ├── auth/ # Authentication UI
│ │ ├── immersive/ # Story viewer & voice chat
│ │ ├── seo/ # SEO components
│ │ └── ui/ # shadcn/ui components
│ ├── config/ # Feature configuration
│ │ └── elevenlabs-agents.ts # Voice agent IDs
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utilities & API clients
│ │ └── i18n/ # Internationalization
│ └── types/ # TypeScript definitions
├── content/
│ └── pdfs/ # Source tourism guides (not tracked; see Content Sources)
├── scripts/ # Data processing & automation
├── supabase/
│ ├── functions/ # Edge Functions (Deno)
│ └── migrations/ # Database schema (96 migrations)
├── docs/ # Project documentation
└── .github/
└── workflows/ # CI/CD (11 workflows)
Automated quality checks run on every push and pull request via GitHub Actions.
| Workflow | Trigger | Description |
|---|---|---|
| CI | Push/PR | Lint, typecheck, verification wiring, test, build |
| E2E | Push/PR | Playwright end-to-end tests |
| Stripe E2E Integration | Stripe-touching PR paths, nightly, manual dispatch | Real Stripe test-mode checkout; manual live gate fails if required secrets are missing |
| Security Audit | Push/PR + daily 08:00 UTC | npm audit --omit=dev --audit-level=moderate |
| Gitleaks | Push/PR + daily | Scans for secrets in git history |
| License Check | PRs | Blocks copyleft/GPL dependencies |
| Lighthouse CI | PRs | Performance & accessibility auditing |
| Bundle Size | PRs | Reports JS bundle sizes as PR comment |
| Knip | PRs | Dead code & unused dependency detection |
| Claude Review | PRs | AI-powered code review |
Dependabot opens weekly PRs for dependency updates.
voyage-3.5, 512 dims)rerank-2.5ElevenLabs Conversational AI powers interactive voice guides for immersive storytelling:
visitor_voice_agent feature flag in admin panelPostgreSQL on Supabase with pgvector for vector similarity search, pg_cron for scheduled maintenance, and pg_net for webhook-driven cache invalidation.
Deno-based functions on Supabase for background tasks:
Tourism information is sourced from 37 official Asturias tourism guides published by Turismo Asturias (Sociedad Pública de Gestión y Promoción Turística y Cultural del Principado de Asturias). Topics covered:
The guides are the property of their publisher and are not distributed with this
repository. To run the content pipeline locally, download the guides from
turismoasturias.es into content/pdfs/ (gitignored)
and run npm run process-pdfs. Story photographs are attributed to their sources in each
story's image_source field; most come from Unsplash.
The source code in this repository is released under the MIT License.
The license covers the code only. Tourism guide content, photographs and other third-party material referenced or processed by this project remain the property of their respective owners and are not covered by the MIT License.
/operator/<id>.<token>docs/hackathon/postman/
walks the same journey over HTTP, including the negative cases.The run redeems a voucher, accepts a quote, pays the deposit, confirms the booking,
cancels it with a refund, and checks the 404, 409 and webhook cases. Its last recorded
run is in run-2026-10-03-local.md.
To use your own PayPal sandbox instead of the mock, follow the approval step in the
same README.
Issue vouchers and operator links for your local stack (both scripts target local by
default; production needs --target production --yes-production and the owner):
npx tsx scripts/booking/create-voucher.ts --label local-test
BOOKING_LINK_SECRET=<the server's secret> \
npx tsx --conditions=react-server scripts/booking/create-operator-link.ts --merchant demo-rutas-del-sella
Tests: npm run test runs the unit suites. The live database suites
(*.postgrest-integration.test.ts) run when the local stack is up and skip otherwise.
npm run eval:booking runs the six-scenario model evaluation against the local stack.
It reads only ANTHROPIC_API_KEY: from the environment
(ANTHROPIC_API_KEY=… npm run eval:booking), else from $HOME/code/paisaxe/.env.local.
Its results are in docs/hackathon/evaluation/.