图片 URL 长相
真实图片 URL 大概是这种公开 Supabase Storage 地址：
https://lehwkihwnlqkavhcspel.supabase.co/storage/v1/object/public/elio-schedules/schedules/42/a1b2c3d4.jpg
前端不用拼 URL，也不用鉴权，后端会直接返回完整 URL。前端只读：
data.image?.url
消息接口
POST https://proapi.banlv-ai.com/api/chat/send
响应 data 固定字段：
{
  "reply": "这是我昨天傍晚的照片，那会儿在外面走了走。",
  "audioUrl": "",
  "messageId": "msg_123",
  "timestamp": 1782271808947,
  "isGuest": false,
  "image": {
    "type": "elio_schedule",
    "url": "https://lehwkihwnlqkavhcspel.supabase.co/storage/v1/object/public/elio-schedules/schedules/42/a1b2c3d4.jpg"
  },
  "lockDetail": {
    "locked": false,
    "showContent": true,
    "showUpgrade": false,
    "reason": null,
    "hint": null,
    "detail": null
  }
}
前端用法：
const d = res.data

if (d.lockDetail?.showContent !== false && d.reply) {
  renderText(d.reply)
}

if (d.image?.url) {
  renderImage(d.image.url)
}

if (d.audioUrl) {
  renderAudio(d.audioUrl)
}

if (d.lockDetail?.showUpgrade) {
  showVipPaywall(d.lockDetail.hint)
}
image.type 目前主要是 elio_schedule，无图时：
"image": { "type": null, "url": null }
被限额/需要充值时：
{
  "reply": "",
  "image": { "type": null, "url": null },
  "lockDetail": {
    "locked": true,
    "showContent": false,
    "showUpgrade": true,
    "reason": "daily_limit",
    "hint": "免费消息额度已用完，开通会员后可以继续聊天。",
    "detail": {
      "type": "daily_msg_limit",
      "usedToday": 3,
      "limit": 3
    }
  }
}
历史接口
GET https://proapi.banlv-ai.com/api/chat/history?limit=50&offset=0
响应结构：
{
  "messages": [
    {
      "role": "user",
      "type": "text",
      "content": "你最近在干嘛",
      "id": "msg_1",
      "created_at": "2026-06-24T10:00:00Z",
      "audioUrl": null,
      "image": { "type": null, "url": null },
      "lockDetail": {
        "locked": false,
        "showContent": true,
        "showUpgrade": false,
        "reason": null,
        "hint": null,
        "detail": null
      }
    },
    {
      "role": "assistant",
      "type": "text",
      "content": "",
      "id": "msg_2",
      "created_at": "2026-06-24T10:00:03Z",
      "audioUrl": null,
      "image": { "type": null, "url": null },
      "lockDetail": {
        "locked": true,
        "showContent": false,
        "showUpgrade": true,
        "reason": "private_message",
        "hint": "他好像想说什么悄悄话，解锁会员才能看到…",
        "detail": { "messageId": "msg_2" }
      }
    }
  ],
  "total": 200,
  "limit": 50,
  "offset": 0,
  "isVip": false
}
历史前端用法：
for (const m of data.messages) {
  if (m.lockDetail?.showContent === false) {
    renderLockedCard(m.lockDetail.hint)
    if (m.lockDetail?.showUpgrade) showVipEntry()
    continue
  }

  renderMessage(m.role, m.content)

  if (m.image?.url) renderImage(m.image.url)
  if (m.audioUrl) renderAudio(m.audioUrl)
}
前端不要再读这些旧字段：mode、relationshipStage、currentMood、intimacyChange、newIntimacy、voiceUrl、imageUrl、imageType、privateLocked、privateHint。统一是 audioUrl、image.url、lockDetail。