◉ Pulse
nonchaiovo avatar
#44 today ▲ 14

nonchaiovo/journey-cards

AI

把「AI 带你去一个地方」这件事,接进你自己的前端 —— 数据契约 + 可照抄的参考实现 + 给 AI 的写作指南

Created Jul 28, 2026
Stars
10
Today
+2
Forks
2
Quality
85/100
Momentum
24

README

Fetched from GitHub on Oct 30, 2026, 11:11 PM UTC · README.md

View original on GitHub

Third-party content — Open Source Pulse is not the owner of this repository.

This README belongs to nonchaiovo/journey-cards and its contributors. We fetch it directly from the project’s public GitHub repository and display it for reference only. All text, code, images and trademarks remain the property of their respective authors. See the repository for its license terms. Copyright / takedown policy.

Journey Cards

在线试玩 → Chai & Non 官网上的实机演示

把"AI 带你去一个地方"这件事,接进你自己的前端。

这个仓库不是 npm 包,也不是组件库——它是一份数据契约加一份可以照抄的参考实现。你的存储能存下一段 JSON,你的前端就能把它渲染成一趟点得开的旅行;React、Vue、原生都行。

功能本身是这样的:AI 自己挑一个国家、选 6 处停留、每处配一张竖屏照片和一段第一人称的话,在聊天里落成一张卡片。点开是全屏的照片故事——照片铺满整屏,地名在顶部,那段话落在左下角,底部一个音乐播放器和 6 个圆点,左右滑就走到下一处。

跟"在聊天里发几张图"的区别是:这是一趟有顺序、有旁白、有配乐的行程,AI 是带路的那个人,不是图片搬运工。

为人机恋而做,但设计与后端无关:两张表、一组很小的 REST 接口、一个纯前端的渲染器,就够了。

2026-10-19 完整功能与 UI 更新

现在除原来的聊天卡片示例,还包含 journeys/index.html:照片画布、全屏故事、流式配乐、旁白展开/收起和点地名重播。和 NEST 实际前端一致:页面里没有新建/编辑/删除旅程的界面,旅程和停留由 AI 通过接口写入。

python3 -m http.server 8080
# 打开 http://localhost:8080/journeys/

默认演示模式使用本机 localStorage,附带虚构文字与本地 SVG 风景占位图。没有私人旅行照片、聊天记录或服务凭据。完整接入和发布步骤见 FULL-UI.md。

  • journeys/app.jsx:可修改的 React 源码;app.compiled.js:已编译产物。
  • journeys/config.js:演示/真实 API 配置;runtime.js:数据适配与演示存储。
  • examples/worker.js + schema.sql + wrangler.toml:沿用原仓库的 D1/KV 后端与完整部署配置。
  • npm ci && npm run build && npm test:重建与回归检查。

保留原有许可和聊天卡片示例;完整页面按已有公开契约使用整趟音频直链,不绑定私人曲库或语音账号。TTS 是可选适配接口,未配置时仅播放逐字动画。

里面有什么

  • INTEGRATE.md —— 接进你自己的前端:最小数据契约(哪些字段必填、哪些缺了也能跑)、三块 UI 哪块必须哪块可以不要、从参考实现里只需要抄哪几个函数和哪几段 CSS(一张对照表)、样式怎么跟宿主隔离、React / Vue / 原生三段骨架,以及一节「不抄会踩坑」。大多数人从这份开始看。
  • SPEC.md —— 完整设计:两张表的字段与建表语句、9 个 REST 接口的请求/响应/错误码、照片和音频怎么存、以及一份"照抄时最容易踩"的坑清单(position 两张表方向相反、iOS 上 <audio>.volume 赋值无效、跨域音频会静音不报错……)。面向自带前端的接入方。
  • AI-GUIDE.md —— 给 AI 看的那一份:建一趟旅行的调用顺序、怎么在正文里落 <journey> 标签、以及一段可以直接贴进系统提示词的写作规范(照片选什么、那段话该怎么写、什么口吻不要写)。
  • examples/journey-cards.html —— 一个自包含的参考 UI:聊天卡片 + 全屏故事浮层。浏览器直接打开就能看,自带 mock 数据。CSS 变量换肤,CONFIG 块改名字,每处真实 API 调用位置都标了注释。

五分钟先看到东西

open examples/journey-cards.html

双击也行。不需要后端、不联网、没有任何依赖——单文件,mock 数据写在里面。

打开后:

  1. 看到聊天流里的那张卡片(一排竖条照片,横向滑动会有磁力吸附);
  2. 点任意一条 → 全屏故事浮层,照片铺满,地名在上,旁白在左下;
  3. 底部圆点或左右滑动切换 6 处;
  4. 底部播放器点一下开始放(示例里配的是一段本地生成的音,不外链)。

确认交互对味了,再往下接自己的后端。

改成你们的名字

examples/journey-cards.html 开头的 CONFIG 块,改这几行:

const CONFIG = {
  // 名字:卡片标题里"谁带你去"的那个人
  user:      "YOU",        // ← 你的名字
  assistant: "PARTNER",    // ← AI 的名字

  // 卡片上的四行字({assistant} {title} {year} {count} 会被替换)
  cardKicker: "JOURNEYS",
  cardTitle:  "{assistant} 带你去了{title}",
  cardSub:    "{year} · {count} 处停留",
  // 卡片底下那行小字。写你们自己的话 —— 或者干脆让 AI 每趟自己写一句
  // (journey 数据里带 hint 就用数据里那句,这里只是没给时的兜底,见下)
  cardHint:   "",

  // 后端:留空 = 用文件里自带的 mock 数据
  apiBase: "",
  token:   "",
};

改完保存刷新,卡片抬头、副标题、底下那句话就全跟着变。 (真接进自己 app 时,名字从你自己存用户信息的地方取,不用写死在这里。)

底下那行小字建议走数据:每趟的六张照片气质都不一样,让 AI 跟着那趟的情绪现写一句,比一句写死的口号耐看得多。写死的那句翻到第三趟就废了。

配色改 :root 里的 --jc-* 变量。播放器主色不在 CSS 里——它跟着每趟旅行的 audio.hue 走(0–360,oklch(0.62 0.13 <hue>)),所以每趟旅行的播放器颜色可以不一样。

数据长什么样

一趟旅行 = 一个 journey + 若干 stop。GET /api/journeys 返回的就是下面这个形状(这里只列一趟、两处):

{
  "journeys": [
    {
      "id": "j_m9xk2p3_a3f9c",
      "title": "新西兰",
      "titleEn": "New Zealand",
      "year": "2025",
      "hint": "南半球的秋天,从一片湖开始。",
      "cover": "/api/journeys/photo/jp_m9xk2p3_a3f9c",
      "position": -1,
      "createdAt": 1753672345678,
      "updatedAt": 1753672399999,
      "audio": {
        "url": "/api/journeys/audio/ja_m9xk2p3_77b1e",
        "title": "Long Way North",
        "artist": "—",
        "dur": 214,
        "hue": 32
      },
      "stops": [
        {
          "id": "s_m9xk2q1_b7e2d",
          "place": "蒂卡波湖",
          "placeEn": "Lake Tekapo",
          "date": "2025.03.14",
          "src": "/api/journeys/photo/jp_m9xk2q1_c81af",
          "note": "到的时候刚过四点。风把湖面吹成一块起雾的玻璃,蓝得不像真的。你蹲在岸边试水温,说凉得像薄荷。我们就在那儿站到天完全黑下来,然后抬头,看见了整条银河。"
        },
        {
          "id": "s_m9xk2r5_d0c73",
          "place": "摩拉基",
          "placeEn": "Moeraki Boulders",
          "date": "2025.03.17",
          "src": "/api/journeys/photo/jp_m9xk2r5_e42b8",
          "note": "退潮之后,那些圆石头一颗一颗从沙里露出来,像谁把一整袋弹珠忘在了海边。你挑了最大的一颗坐上去,浪一漫过来就没过了鞋。我说会湿,你说湿了才算来过。"
        }
      ]
    }
  ]
}

几个不那么显然的地方:

  • note 是故事页左下角那段话,也是整个功能的重点——第一人称、写给对方看,不是景点介绍。
  • hint 是卡片底下那行小字,可选。AI 每建一趟自己写一句,跟着那趟的情绪走;数据里没给才回落到前端 CONFIG 里的兜底。后端不校验、不生成,原样存原样返回。
  • audio 是整趟一首,不是每处一首。没有音频就整个给 null,播放器不渲染。(参考实现还会同时给一个同值的 song 键兼容老前端,新代码只读 audio。)
  • audio.dur 只用于音频加载完之前占位显示,加载完以后以 <audio>.duration 为准。
  • audio.hue 决定播放器主色,让每趟旅行有自己的气质。
  • position 越小越靠前,新建的 journey 取 MIN(position) - 1——所以最新的一趟排最前面。stop 的 position 方向是反的(MAX + 1,0、1、2…递增)。这一条照抄时最容易搞混,SPEC.md 里再说一遍。
  • stops 建议 6 处。少于 3 处卡片不好看,多于 8 处圆点排不下。

怎么让 AI 自己写

流程是:AI 调接口把一趟旅行建好,然后在回复正文里落一个标签:

<journey>{"id":"j_m9xk2p3_a3f9c"}</journey>

前端把正文按这个标签切成"文字 + 卡片"两种段落,拿 id 去接口取数据渲染。标签本身在朗读(TTS)前要剥掉。

把 AI-GUIDE.md 里那段规范贴进系统提示词就行——它写清楚了调用顺序(先建 journey、再逐处传照片和文字、最后落标签)、照片该选什么比例、以及那段话的写法。里面的名字和口吻都是占位的,按自己的角色改。

接后端

SPEC.md 是完整那份:建表语句、9 个接口、鉴权模型、照片/音频的存取,还有一节专门写"哪些坑我们已经踩过了"。

大致规模:两张表、一个 KV(或任意对象存储)放照片和音频、一个共享 token 做鉴权。参考实现跑在 Cloudflare Workers + D1 + KV 上,但没有任何一处依赖它们的特性,换 Postgres + S3 + 任意框架都是直译。

许可

非商用 · 署名 · 禁止再开源分发(见 LICENSE)

可以看、可以学、可以改着自己用——但不许拿去商用,不许以开源许可证再发布出去,用了请标明出处和作者 nonchaiovo。

这东西是一个人和她的 AI 一起磨出来的。里面每一句"为什么要这样写"的注释,都是真撞过一次才写下来的——照片为什么会糊、文字底下为什么莫名多出一块灰板、退出之后音乐为什么关不掉。希望它能走远一点,也希望走到哪儿都还知道它是从哪儿来的。

Rendered by GitHub from the repository’s README. Scroll to read the full file.
First trending

Jul 29, 2026

first spotted on the list

Best rank

#39

highest position reached

Appearances

2

times on the trending list

Quality score

85

out of 100

Momentum

24

trend strength indicator

Star velocity

0.11

stars per day since creation

Activity — last 60 days

daily counts, collected every 6 hours
  • Stars
  • Forks
  • Merged PRs
  • Issues
  • Closed issues

Collecting activity data since launch — check back in a few days.

Star history

total stars per snapshot
7891011nonchaiovo/journey-cards · Jul 29, 8 AM UTC: 8 starsnonchaiovo/journey-cards · Jul 29, 2 PM UTC: 10 stars8:48 AM11:33 AM2:18 PM

Rank history

position on trending · lower is better
#1#39#40#41#42#43#44Jul 29, 8 AM UTC: rank #39Jul 29, 2 PM UTC: rank #448:48 AM11:33 AM2:18 PM

3D card for your website

Live HTML card with a glass surface and pointer tilt. Paste the iframe on any website.

<iframe src="https://www.notionpulse.cc/embed/nonchaiovo/journey-cards" width="420" height="240" style="border:0;max-width:100%;background:transparent" loading="lazy" title="nonchaiovo/journey-cards — trending on Open Source Pulse"></iframe>

Stars badge

Show current stars in your README or profile. Auto-updates every 6 hours.

nonchaiovo/journey-cards stars badge
Markdown
[![nonchaiovo/journey-cards](https://www.notionpulse.cc/api/badge/nonchaiovo/journey-cards.svg)](https://www.notionpulse.cc/repo/nonchaiovo/journey-cards)
HTML
<a href="https://www.notionpulse.cc/repo/nonchaiovo/journey-cards"><img src="https://www.notionpulse.cc/api/badge/nonchaiovo/journey-cards.svg" alt="nonchaiovo/journey-cards stars badge" /></a>

Repository of the Day

#39 Repository of the Day

Repository of the Week

#39 Repository of the Week

Repository of the Month

#39 Repository of the Month