V1.0: Class 1 fax — real-world 4-page send to external number confirmed
Core features: - Class 1 T.30 protocol: full send/receive implementation - HDLC: DLE-stuffing, FCS strip, USR5637 bit-reversal handling - T.4 MH encoder/decoder (1728px A4 standard) - Document pipeline: PDF (Ghostscript), PNG, TIFF input - Width clamping: US Letter 1734px → 1728px fax standard - Cover page: CJK rasterization (TW/CN/JP/EN), TIFF + HTML output - OCR verification: Tesseract 5 with eng+chi_tra, CJK space-tolerant - API server (axum): health, send, jobs, cover, retry, cancel - Background worker: auto-poll queue, speed fallback, retry policy - Modem detection, pool management Real-world test results (2026-07-23): - V90 → 25153038: 4 pages, V.17 12000 bps, 2:33 ✅ - USR5637 → 25153038: 4 pages, V.17 12000 bps, 2:26 ✅ - Both faxes confirmed received on remote machine Tested: loopback (100% pixel match), multi-page, all input formats, cover pages, OCR verify, API endpoints, worker processing. 13 unit tests pass, 0 new clippy warnings.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { addressBookApi } from '../api/addressBook'
|
||||
import type { Contact, Group, CreateContactRequest, UpdateContactRequest, CreateGroupRequest } from '../types/addressBook'
|
||||
|
||||
export const useAddressBookStore = defineStore('addressBook', () => {
|
||||
const contacts = ref<Contact[]>([])
|
||||
const groups = ref<Group[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function fetchContacts(query?: string, category?: string) {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await addressBookApi.getContacts({ query, category })
|
||||
contacts.value = response.data.contacts
|
||||
} catch (e) {
|
||||
error.value = 'Failed to fetch contacts'
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createContact(data: CreateContactRequest) {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await addressBookApi.createContact(data)
|
||||
await fetchContacts()
|
||||
return response.data
|
||||
} catch (e) {
|
||||
error.value = 'Failed to create contact'
|
||||
console.error(e)
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateContact(id: string, data: UpdateContactRequest) {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await addressBookApi.updateContact(id, data)
|
||||
await fetchContacts()
|
||||
return response.data
|
||||
} catch (e) {
|
||||
error.value = 'Failed to update contact'
|
||||
console.error(e)
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteContact(id: string) {
|
||||
try {
|
||||
loading.value = true
|
||||
await addressBookApi.deleteContact(id)
|
||||
await fetchContacts()
|
||||
} catch (e) {
|
||||
error.value = 'Failed to delete contact'
|
||||
console.error(e)
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchGroups() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await addressBookApi.getGroups()
|
||||
groups.value = response.data.groups
|
||||
} catch (e) {
|
||||
error.value = 'Failed to fetch groups'
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createGroup(data: CreateGroupRequest) {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await addressBookApi.createGroup(data)
|
||||
await fetchGroups()
|
||||
return response.data
|
||||
} catch (e) {
|
||||
error.value = 'Failed to create group'
|
||||
console.error(e)
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteGroup(id: string) {
|
||||
try {
|
||||
loading.value = true
|
||||
await addressBookApi.deleteGroup(id)
|
||||
await fetchGroups()
|
||||
} catch (e) {
|
||||
error.value = 'Failed to delete group'
|
||||
console.error(e)
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function addContactToGroup(contactId: string, groupId: string) {
|
||||
try {
|
||||
loading.value = true
|
||||
await addressBookApi.addContactToGroup(contactId, groupId)
|
||||
} catch (e) {
|
||||
error.value = 'Failed to add contact to group'
|
||||
console.error(e)
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
contacts,
|
||||
groups,
|
||||
loading,
|
||||
error,
|
||||
fetchContacts,
|
||||
createContact,
|
||||
updateContact,
|
||||
deleteContact,
|
||||
fetchGroups,
|
||||
createGroup,
|
||||
deleteGroup,
|
||||
addContactToGroup,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,93 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { faxApi } from '../api/client'
|
||||
import type { ServerStatus, FaxJob, ModemStatus } from '../types'
|
||||
|
||||
export const useFaxStore = defineStore('fax', () => {
|
||||
const status = ref<ServerStatus | null>(null)
|
||||
const jobs = ref<FaxJob[]>([])
|
||||
const modems = ref<ModemStatus[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function fetchStatus() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await faxApi.getStatus()
|
||||
status.value = response.data
|
||||
} catch (e) {
|
||||
error.value = 'Failed to fetch status'
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJobs() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await faxApi.getJobs()
|
||||
jobs.value = response.data
|
||||
} catch (e) {
|
||||
error.value = 'Failed to fetch jobs'
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchModems() {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await faxApi.getModems()
|
||||
modems.value = response.data
|
||||
} catch (e) {
|
||||
error.value = 'Failed to fetch modems'
|
||||
console.error(e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function sendFax(to_number: string, options: any = {}) {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await faxApi.sendFax({ to_number, ...options })
|
||||
await fetchJobs()
|
||||
return response.data
|
||||
} catch (e) {
|
||||
error.value = 'Failed to send fax'
|
||||
console.error(e)
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelJob(id: string) {
|
||||
try {
|
||||
loading.value = true
|
||||
await faxApi.cancelJob(id)
|
||||
await fetchJobs()
|
||||
} catch (e) {
|
||||
error.value = 'Failed to cancel job'
|
||||
console.error(e)
|
||||
throw e
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
jobs,
|
||||
modems,
|
||||
loading,
|
||||
error,
|
||||
fetchStatus,
|
||||
fetchJobs,
|
||||
fetchModems,
|
||||
sendFax,
|
||||
cancelJob,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user