feat(web): route turns through runtime bridge
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"story_id": "story_nana_demo",
|
||||||
|
"current_node": "node_001",
|
||||||
|
"current_branch": "branch_main",
|
||||||
|
"world_flags": {},
|
||||||
|
"relationships": {
|
||||||
|
"nana->player": {
|
||||||
|
"affinity": 48,
|
||||||
|
"trust": 34,
|
||||||
|
"hope": 31,
|
||||||
|
"respect": 45,
|
||||||
|
"intimacy": 26,
|
||||||
|
"attachment": 42
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relationship_states": [],
|
||||||
|
"promises": [],
|
||||||
|
"knowledge": [
|
||||||
|
{
|
||||||
|
"id": "knowledge_station_clock",
|
||||||
|
"observer": "player",
|
||||||
|
"subject": "old_station_platform",
|
||||||
|
"fact": "站钟停在四点十七分,似乎并非自然损坏。",
|
||||||
|
"certainty": "confirmed",
|
||||||
|
"source": "玩家进入站台时亲眼观察",
|
||||||
|
"learned_at": "node_001",
|
||||||
|
"last_verified_at": "node_001"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "item_flashlight_001",
|
||||||
|
"spec_ref": "nana.item.old_flashlight",
|
||||||
|
"owner": "player",
|
||||||
|
"holder": "player",
|
||||||
|
"placement": "bag",
|
||||||
|
"quantity": 1,
|
||||||
|
"condition": "worn",
|
||||||
|
"state_tags": [],
|
||||||
|
"acquisition": {
|
||||||
|
"mode": "initial",
|
||||||
|
"from": null,
|
||||||
|
"at_node": "node_001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"clocks": [
|
||||||
|
{
|
||||||
|
"id": "clock_dawn",
|
||||||
|
"label": "天亮逼近",
|
||||||
|
"value": 0,
|
||||||
|
"max": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"checks": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import initialView from "../../fixtures/player-view/initial.json";
|
||||||
|
import type { PlayerView, TurnRequest, TurnResult } from "@contracts";
|
||||||
|
|
||||||
|
const invokeMock = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
|
vi.mock("@tauri-apps/api/core", () => ({
|
||||||
|
invoke: invokeMock
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { submitTurn } from "./bridge";
|
||||||
|
|
||||||
|
const request: TurnRequest = {
|
||||||
|
storyId: initialView.storyId,
|
||||||
|
branchId: initialView.branchId,
|
||||||
|
expectedNodeId: initialView.nodeId,
|
||||||
|
actionId: "action_bridge_test",
|
||||||
|
intent: "continue",
|
||||||
|
input: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Tauri bridge", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
invokeMock.mockReset();
|
||||||
|
window.__TAURI_INTERNALS__ = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete window.__TAURI_INTERNALS__;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends only TurnRequest to the submit_turn command", async () => {
|
||||||
|
const expected: TurnResult = {
|
||||||
|
committedNodeId: "node_002",
|
||||||
|
playerView: {
|
||||||
|
...(initialView as PlayerView),
|
||||||
|
nodeId: "node_002"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
invokeMock.mockResolvedValue(expected);
|
||||||
|
|
||||||
|
await expect(submitTurn(request, initialView as PlayerView)).resolves.toEqual(expected);
|
||||||
|
expect(invokeMock).toHaveBeenCalledWith("submit_turn", { request });
|
||||||
|
});
|
||||||
|
});
|
||||||
+19
-1
@@ -1,7 +1,15 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
import initialView from "../../fixtures/player-view/initial.json";
|
import initialView from "../../fixtures/player-view/initial.json";
|
||||||
import type { AppInfo, DemoPackSummary, PlayerView } from "@contracts";
|
import type {
|
||||||
|
AppInfo,
|
||||||
|
DemoPackSummary,
|
||||||
|
PlayerView,
|
||||||
|
TurnRequest,
|
||||||
|
TurnResult
|
||||||
|
} from "@contracts";
|
||||||
|
|
||||||
|
import { submitDemoTurn } from "./turnAdapter";
|
||||||
|
|
||||||
function isTauri(): boolean {
|
function isTauri(): boolean {
|
||||||
return typeof window !== "undefined" && window.__TAURI_INTERNALS__ !== undefined;
|
return typeof window !== "undefined" && window.__TAURI_INTERNALS__ !== undefined;
|
||||||
@@ -40,3 +48,13 @@ export async function getDemoPackSummary(): Promise<DemoPackSummary> {
|
|||||||
}
|
}
|
||||||
return invoke<DemoPackSummary>("get_demo_pack_summary");
|
return invoke<DemoPackSummary>("get_demo_pack_summary");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function submitTurn(
|
||||||
|
request: TurnRequest,
|
||||||
|
currentView: PlayerView
|
||||||
|
): Promise<TurnResult> {
|
||||||
|
if (!isTauri()) {
|
||||||
|
return submitDemoTurn(request, currentView);
|
||||||
|
}
|
||||||
|
return invoke<TurnResult>("submit_turn", { request });
|
||||||
|
}
|
||||||
|
|||||||
+2
-3
@@ -2,8 +2,7 @@ import { onMounted, ref } from "vue";
|
|||||||
|
|
||||||
import type { AppInfo, DemoPackSummary, PlayerView, TurnIntent, TurnRequest } from "@contracts";
|
import type { AppInfo, DemoPackSummary, PlayerView, TurnIntent, TurnRequest } from "@contracts";
|
||||||
|
|
||||||
import { getAppInfo, getDemoPackSummary, getDemoPlayerView } from "./bridge";
|
import { getAppInfo, getDemoPackSummary, getDemoPlayerView, submitTurn as submitRuntimeTurn } from "./bridge";
|
||||||
import { submitDemoTurn } from "./turnAdapter";
|
|
||||||
|
|
||||||
export function useDemo() {
|
export function useDemo() {
|
||||||
const appInfo = ref<AppInfo | null>(null);
|
const appInfo = ref<AppInfo | null>(null);
|
||||||
@@ -51,7 +50,7 @@ export function useDemo() {
|
|||||||
lastSubmittedIntent.value = intent;
|
lastSubmittedIntent.value = intent;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await submitDemoTurn(request, currentView);
|
const result = await submitRuntimeTurn(request, currentView);
|
||||||
playerView.value = result.playerView;
|
playerView.value = result.playerView;
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
turnError.value = reason instanceof Error ? reason.message : String(reason);
|
turnError.value = reason instanceof Error ? reason.message : String(reason);
|
||||||
|
|||||||
Reference in New Issue
Block a user