Initial commit: AI Dungeon clone (FastAPI backend + React frontend)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFsGHju9szibJJa2YJcdbg
This commit is contained in:
parththakkar106
2026-07-06 16:08:19 +05:30
co-authored by Claude Fable 5
commit db9f904222
57 changed files with 7804 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
async function request(path, options = {}) {
const resp = await fetch(`/api${path}`, {
headers: { 'Content-Type': 'application/json' },
...options,
})
if (!resp.ok) {
let detail = resp.statusText
try {
const body = await resp.json()
detail = body.detail || detail
} catch { /* non-JSON error body */ }
throw new Error(detail)
}
if (resp.status === 204) return null
return resp.json()
}
// POSTs to an SSE endpoint and dispatches events: {type: 'player'|'chunk'|'done'|'error', ...}
async function streamSSE(path, payload, onEvent, signal) {
const resp = await fetch(`/api${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
signal,
})
if (!resp.ok) {
let detail = resp.statusText
try { detail = (await resp.json()).detail || detail } catch { /* non-JSON */ }
throw new Error(detail)
}
const reader = resp.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
for (;;) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const events = buffer.split('\n\n')
buffer = events.pop() // keep incomplete tail
for (const block of events) {
for (const line of block.split('\n')) {
if (line.startsWith('data:')) onEvent(JSON.parse(line.slice(5)))
}
}
}
}
export const api = {
// Scenarios
listScenarios: () => request('/scenarios'),
getScenario: (id) => request(`/scenarios/${id}`),
createScenario: (data) => request('/scenarios', { method: 'POST', body: JSON.stringify(data) }),
updateScenario: (id, data) => request(`/scenarios/${id}`, { method: 'PATCH', body: JSON.stringify(data) }),
deleteScenario: (id) => request(`/scenarios/${id}`, { method: 'DELETE' }),
// Adventures
listAdventures: () => request('/adventures'),
getAdventure: (id) => request(`/adventures/${id}`),
createAdventure: (data) => request('/adventures', { method: 'POST', body: JSON.stringify(data) }),
updateAdventure: (id, data) => request(`/adventures/${id}`, { method: 'PATCH', body: JSON.stringify(data) }),
deleteAdventure: (id) => request(`/adventures/${id}`, { method: 'DELETE' }),
updateAction: (advId, actionId, text) =>
request(`/adventures/${advId}/actions/${actionId}`, { method: 'PATCH', body: JSON.stringify({ text }) }),
deleteAction: (advId, actionId) =>
request(`/adventures/${advId}/actions/${actionId}`, { method: 'DELETE' }),
sendAction: (advId, payload, handlers, signal) =>
streamSSE(`/adventures/${advId}/actions`, payload, handlers, signal),
retry: (advId, handlers, signal) => streamSSE(`/adventures/${advId}/retry`, {}, handlers, signal),
exportAdventure: (id) => request(`/adventures/${id}/export`),
importAdventure: (bundle) => request('/adventures/import', { method: 'POST', body: JSON.stringify(bundle) }),
undo: (advId) => request(`/adventures/${advId}/undo`, { method: 'POST' }),
getAdventureContext: (advId) => request(`/adventures/${advId}/context`),
getActionContext: (advId, actionId) => request(`/adventures/${advId}/actions/${actionId}/context`),
// Memory bank
listMemories: (advId) => request(`/adventures/${advId}/memories`),
createMemory: (advId, text) =>
request(`/adventures/${advId}/memories`, { method: 'POST', body: JSON.stringify({ text }) }),
updateMemory: (advId, memoryId, data) =>
request(`/adventures/${advId}/memories/${memoryId}`, { method: 'PATCH', body: JSON.stringify(data) }),
deleteMemory: (advId, memoryId) =>
request(`/adventures/${advId}/memories/${memoryId}`, { method: 'DELETE' }),
listAdventureScripts: (advId) => request(`/adventures/${advId}/scripts`),
updateAdventureScript: (advId, scriptId, data) =>
request(`/adventures/${advId}/scripts/${scriptId}`, { method: 'PATCH', body: JSON.stringify(data) }),
// Scripts
listScripts: () => request('/scripts'),
getScript: (id) => request(`/scripts/${id}`),
createScript: (data) => request('/scripts', { method: 'POST', body: JSON.stringify(data) }),
updateScript: (id, data) => request(`/scripts/${id}`, { method: 'PATCH', body: JSON.stringify(data) }),
deleteScript: (id) => request(`/scripts/${id}`, { method: 'DELETE' }),
testScript: (id, data) => request(`/scripts/${id}/test`, { method: 'POST', body: JSON.stringify(data) }),
exportScript: (id) => request(`/scripts/${id}/export`),
importScript: (bundle) => request('/scripts/import', { method: 'POST', body: JSON.stringify(bundle) }),
// Scenario import/export
exportScenario: (id) => request(`/scenarios/${id}/export`),
importScenario: (bundle) => request('/scenarios/import', { method: 'POST', body: JSON.stringify(bundle) }),
// Story cards
createStoryCard: (data) => request('/story-cards', { method: 'POST', body: JSON.stringify(data) }),
updateStoryCard: (id, data) => request(`/story-cards/${id}`, { method: 'PATCH', body: JSON.stringify(data) }),
deleteStoryCard: (id) => request(`/story-cards/${id}`, { method: 'DELETE' }),
// Debug
getDebugRequests: () => request('/debug/requests'),
// Settings
getSettings: () => request('/settings'),
updateSettings: (data) => request('/settings', { method: 'PUT', body: JSON.stringify(data) }),
testConnection: () => request('/settings/test', { method: 'POST' }),
}