Initial scaffold: Flask/SQLite backend, React/Vite PWA frontend
Backend: receipt upload -> LLM vision extraction (OpenAI-compatible, provider-agnostic), item review/edit, per-group splitting against Cospend projects/members, highlight+upload via WebDAV, public share link, bill creation via Cospend's OCS API (verified against real source, not just doc summaries). Frontend: capture -> review -> group -> summary flow as an installable PWA. install.sh / run.sh (venv + npm, tmux session) instead of Docker, per the ~/Projects/gain pattern.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import type { CospendProject, Group, Item, Member, Receipt, SubmitResult } from './types'
|
||||
|
||||
const BASE = import.meta.env.VITE_API_BASE_URL ?? ''
|
||||
|
||||
async function req<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${BASE}/api${path}`, {
|
||||
...init,
|
||||
headers: init?.body instanceof FormData ? init.headers : { 'Content-Type': 'application/json', ...init?.headers },
|
||||
})
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}))
|
||||
throw new Error(body.error || `${res.status} ${res.statusText}`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export const api = {
|
||||
uploadReceipt(
|
||||
file: File | Blob,
|
||||
): Promise<{ id: string; items: Item[]; store_name: string | null; date: string }> {
|
||||
const form = new FormData()
|
||||
form.append('image', file, 'receipt.jpg')
|
||||
return req('/receipts', { method: 'POST', body: form })
|
||||
},
|
||||
|
||||
getReceipt(id: string): Promise<Receipt> {
|
||||
return req(`/receipts/${id}`)
|
||||
},
|
||||
|
||||
updateItems(id: string, items: Item[]): Promise<Receipt> {
|
||||
return req(`/receipts/${id}/items`, { method: 'PATCH', body: JSON.stringify({ items }) })
|
||||
},
|
||||
|
||||
updateMeta(id: string, meta: { store_name?: string; date?: string }): Promise<Receipt> {
|
||||
return req(`/receipts/${id}/meta`, { method: 'PATCH', body: JSON.stringify(meta) })
|
||||
},
|
||||
|
||||
getProjects(): Promise<{ projects: CospendProject[]; default_project_id: string | null }> {
|
||||
return req('/cospend/projects')
|
||||
},
|
||||
|
||||
getMembers(projectId: string): Promise<Member[]> {
|
||||
return req(`/cospend/projects/${projectId}/members`)
|
||||
},
|
||||
|
||||
createGroup(
|
||||
receiptId: string,
|
||||
group: {
|
||||
name: string
|
||||
cospend_project_id: string
|
||||
payer_member_id: string
|
||||
member_ids: string[]
|
||||
item_ids: string[]
|
||||
},
|
||||
): Promise<{ id: string }> {
|
||||
return req(`/receipts/${receiptId}/groups`, { method: 'POST', body: JSON.stringify(group) })
|
||||
},
|
||||
|
||||
submitGroup(receiptId: string, groupId: string): Promise<SubmitResult> {
|
||||
return req(`/receipts/${receiptId}/groups/${groupId}/submit`, { method: 'POST' })
|
||||
},
|
||||
|
||||
listGroups(receiptId: string): Promise<Group[]> {
|
||||
return req(`/receipts/${receiptId}/groups`)
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user