feat: Add .env.example configuration file and integrate Redis service into Docker Compose for improved task management. Update desktop application to support document indexing and extraction workflows, enhancing user experience with a new extraction wizard and stepper components. Revise README and documentation for clarity on setup and usage.

This commit is contained in:
erik
2026-07-05 22:39:07 -05:00
parent 9aca926988
commit d95f8f6ad8
70 changed files with 9807 additions and 499 deletions
@@ -0,0 +1,112 @@
package pipeline
import (
"math"
"sort"
)
// ClusterRows runs 1D DBSCAN on Y coordinates (parity with sklearn DBSCAN).
func ClusterRows(boxes []indexedBox, eps float64, minSamples int) {
if len(boxes) == 0 {
return
}
ys := make([]float64, len(boxes))
for i, b := range boxes {
ys[i] = float64(b.Y)
}
labels := dbscan1D(ys, eps, minSamples)
for i := range boxes {
boxes[i].Cluster = labels[i]
}
}
func dbscan1D(points []float64, eps float64, minSamples int) []int {
n := len(points)
labels := make([]int, n)
for i := range labels {
labels[i] = -1
}
clusterID := 0
visited := make([]bool, n)
for i := 0; i < n; i++ {
if visited[i] {
continue
}
visited[i] = true
neighbors := regionQuery(points, i, eps)
if len(neighbors) < minSamples {
continue
}
labels[i] = clusterID
seed := append([]int(nil), neighbors...)
for j := 0; j < len(seed); j++ {
q := seed[j]
if !visited[q] {
visited[q] = true
neighborsQ := regionQuery(points, q, eps)
if len(neighborsQ) >= minSamples {
seed = appendUnique(seed, neighborsQ)
}
}
if labels[q] == -1 {
labels[q] = clusterID
}
}
clusterID++
}
return labels
}
func regionQuery(points []float64, idx int, eps float64) []int {
var neighbors []int
for i, p := range points {
if math.Abs(p-points[idx]) <= eps {
neighbors = append(neighbors, i)
}
}
return neighbors
}
func appendUnique(seed, add []int) []int {
inSeed := make(map[int]bool, len(seed))
for _, v := range seed {
inSeed[v] = true
}
for _, v := range add {
if !inSeed[v] {
seed = append(seed, v)
inSeed[v] = true
}
}
return seed
}
type indexedBox struct {
Text string
X int
Y int
W int
H int
Cluster int
}
func groupByCluster(boxes []indexedBox) map[int][]indexedBox {
groups := make(map[int][]indexedBox)
for _, b := range boxes {
if b.Cluster < 0 {
continue
}
groups[b.Cluster] = append(groups[b.Cluster], b)
}
return groups
}
func sortClusters(groups map[int][]indexedBox) []int {
keys := make([]int, 0, len(groups))
for k := range groups {
keys = append(keys, k)
}
sort.Ints(keys)
return keys
}