Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -3205,6 +3205,41 @@ Agent 在执行创建操作时,先生成预览供用户确认,确认后存

---

#### `POST /api/worldbuilding/:wid/timeline` — 手动记录时间线事件

记录任意世界时间点的事件,不校验"只能向前"——支持回忆/闪回/补录。

**路径参数**:`wid` — 世界 ID

**请求体**:

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `world_time` | string | 是 | 事件发生的世界时间(如 `"第2日午"`) |
| `description` | string | 是 | 事件描述 |
| `recorded_by` | string | 否 | 记录者,默认 `"user"` |
| `affected_character_ids` | string[] | 否 | 受影响的角色 ID 列表 |
| `related_scene_ids` | string[] | 否 | 关联场景 ID 列表 |

**响应 `201`**:

| 字段 | 类型 | 说明 |
|------|------|------|
| `ok` | boolean | 操作是否成功 |
| `event.id` | string | 事件 ID |
| `event.world_time` | string | 世界时间 |
| `event.description` | string | 事件描述 |
| `event.recorded_by` | string | 记录者 |
| `event.affected_character_ids` | string[] | 受影响的角色 ID |
| `event.related_scene_ids` | string[] | 关联场景 ID |

**`400`**:`invalid_request` — 缺少必填字段或 JSON 解析失败
**`404`**:`world_not_found`

> **与 `POST /timeline/advance` 的区别**:`/timeline` 不校验时间先后,可记录过去/未来的任意事件;`/timeline/advance` 要求新时间严格晚于当前世界时间。

---

#### `POST /api/worldbuilding/:wid/timeline/advance` — 推进世界时间

推进世界时间并可选记录时间线事件。替代旧的 `/api/worldbuilding/:wid/time/advance` 端点。
Expand Down Expand Up @@ -3304,7 +3339,7 @@ Agent 在执行创建操作时,先生成预览供用户确认,确认后存
| PUT | `/api/workspace/files/content` | 保存文件内容 |
| POST | `/api/workspace/open` | 打开文件/目录 |

### 世界观(95 个)
### 世界观(96 个)

| 方法 | 路径 | 分类 |
|------|------|------|
Expand Down Expand Up @@ -3403,8 +3438,9 @@ Agent 在执行创建操作时,先生成预览供用户确认,确认后存
| GET | `/api/worldbuilding/:wid/timeline` | Timeline |
| GET | `/api/worldbuilding/:wid/timeline/events/:eid` | Timeline |
| POST | `/api/worldbuilding/:wid/timeline/advance` | Timeline |
| POST | `/api/worldbuilding/:wid/timeline` | Timeline |

**总计:123 个 API 端点**(28 core + 95 worldbuilding),加 1 个 SSE 流端点,加 1 个静态文件挂载(`/`)。
**总计:124 个 API 端点**(28 core + 96 worldbuilding),加 1 个 SSE 流端点,加 1 个静态文件挂载(`/`)。

---

Expand Down
1 change: 1 addition & 0 deletions libs/http/include/merak/worldbuilding_http_handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class WorldbuildingHttpHandler {
void handle_timeline_list(const httplib::Request&, httplib::Response&);
void handle_timeline_event_get(const httplib::Request&, httplib::Response&);
void handle_timeline_advance(const httplib::Request&, httplib::Response&);
void handle_timeline_add_event(const httplib::Request&, httplib::Response&);

// Foreshadowing
void handle_foreshadow_list(const httplib::Request&, httplib::Response&);
Expand Down
44 changes: 44 additions & 0 deletions libs/http/src/worldbuilding_http_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ void WorldbuildingHttpHandler::install_routes(httplib::Server& server) {
[this](const auto& req, auto& res) { handle_timeline_event_get(req, res); });
server.Get(R"(/api/worldbuilding/([^/]+)/timeline)",
[this](const auto& req, auto& res) { handle_timeline_list(req, res); });
server.Post(R"(/api/worldbuilding/([^/]+)/timeline)",
[this](const auto& req, auto& res) { handle_timeline_add_event(req, res); });
server.Post(R"(/api/worldbuilding/([^/]+)/timeline/advance)",
[this](const auto& req, auto& res) { handle_timeline_advance(req, res); });

Expand Down Expand Up @@ -1522,6 +1524,48 @@ void WorldbuildingHttpHandler::handle_timeline_advance(const httplib::Request& r
}
}

void WorldbuildingHttpHandler::handle_timeline_add_event(const httplib::Request& req, httplib::Response& res) {
try {
std::string wid = req.matches[1];
auto body = nlohmann::json::parse(req.body);

auto world = service_->worlds().get_world(wid);
if (!world) {
error_response(res, "World not found", 404, "world_not_found");
return;
}

worldbuilding::TimelineEvent event;
event.world_time = body.at("world_time");
event.description = body.at("description");
event.recorded_by = body.value("recorded_by", "user");
if (body.contains("affected_character_ids") && body["affected_character_ids"].is_array()) {
for (const auto& id : body["affected_character_ids"])
event.affected_character_ids.push_back(id.get<std::string>());
}
if (body.contains("related_scene_ids") && body["related_scene_ids"].is_array()) {
for (const auto& id : body["related_scene_ids"])
event.related_scene_ids.push_back(id.get<std::string>());
}

auto recorded = service_->narrative().record_timeline_event(wid, std::move(event));

json_response(res, {
{"ok", true},
{"event", {
{"id", recorded.id},
{"world_time", recorded.world_time},
{"description", recorded.description},
{"recorded_by", recorded.recorded_by},
{"affected_character_ids", recorded.affected_character_ids},
{"related_scene_ids", recorded.related_scene_ids}
}}
}, 201);
} catch (const std::exception& e) {
error_response(res, e.what(), 400);
}
}


// --- Foreshadowing handlers ---

Expand Down
Loading