Files
Loki/internal/loki/chat_diff.go
T
Loki de6551a153 Rebaptise AJEAN en Loki (fork, lignée conservée)
- module github.com/R0m1k3/Loki, cmd/loki, internal/loki (package loki)
- LOKI_HOME, LOKI_MODEL_DIRS, LOKI_SERVICE, LOKI_DL_CONNS ; /etc/loki ;
  units loki-engine / loki-ui ; binaire et aide CLI
- updateRepo pointe sur R0m1k3/Loki (l'auto-update ne tirera plus les
  binaires AJEAN amont)

Conservé à l'identique : le domaine ajean.link (service de tunnel amont),
les littéraux de migration 0.7.x (migrate_07.go), RELEASE_NOTES.md et
LICENSE (historique et licence de l'amont).

go build/vet/test : verts.
2026-08-14 21:33:15 +00:00

122 lines
3.2 KiB
Go

package loki
// chat_diff.go — petit diff ligne à ligne pour l'interface : quand l'IA modifie
// un fichier (outil `edit`) ou une page de mémoire (`mem_edit`, `mem_add`), on
// envoie à l'UI le détail des lignes ajoutées et retirées, qu'elle affiche en
// vert (+) et rouge (-). Aucune dépendance : LCS classique sur les lignes, avec
// des garde-fous pour ne jamais transformer un gros remplacement en pavé.
import "strings"
const (
diffMaxLines = 400 // au-delà, on compare sans détail (trop gros / trop lent)
diffMaxShown = 120 // lignes envoyées à l'UI (le reste est résumé)
)
// DiffLine est une ligne de diff : Op vaut " " (contexte), "-" ou "+".
type DiffLine struct {
Op string `json:"op"`
Text string `json:"text"`
}
// lineDiff compare deux blocs de texte ligne à ligne. Le résultat garde les
// lignes communes comme contexte : c'est ce qui rend le changement lisible
// quand l'IA ne touche qu'un mot au milieu d'un paragraphe.
func lineDiff(oldText, newText string) []DiffLine {
a := splitLines(oldText)
b := splitLines(newText)
// Blocs énormes : on ne calcule pas la LCS (coût quadratique), on montre
// simplement l'ancien en retrait et le nouveau en ajout.
if len(a) > diffMaxLines || len(b) > diffMaxLines {
out := make([]DiffLine, 0, len(a)+len(b))
for _, l := range a {
out = append(out, DiffLine{Op: "-", Text: l})
}
for _, l := range b {
out = append(out, DiffLine{Op: "+", Text: l})
}
return capLines(out)
}
// LCS : table des longueurs, puis remontée.
n, m := len(a), len(b)
lcs := make([][]int, n+1)
for i := range lcs {
lcs[i] = make([]int, m+1)
}
for i := n - 1; i >= 0; i-- {
for j := m - 1; j >= 0; j-- {
if a[i] == b[j] {
lcs[i][j] = lcs[i+1][j+1] + 1
} else if lcs[i+1][j] >= lcs[i][j+1] {
lcs[i][j] = lcs[i+1][j]
} else {
lcs[i][j] = lcs[i][j+1]
}
}
}
var out []DiffLine
i, j := 0, 0
for i < n && j < m {
switch {
case a[i] == b[j]:
out = append(out, DiffLine{Op: " ", Text: a[i]})
i++
j++
case lcs[i+1][j] >= lcs[i][j+1]:
out = append(out, DiffLine{Op: "-", Text: a[i]})
i++
default:
out = append(out, DiffLine{Op: "+", Text: b[j]})
j++
}
}
for ; i < n; i++ {
out = append(out, DiffLine{Op: "-", Text: a[i]})
}
for ; j < m; j++ {
out = append(out, DiffLine{Op: "+", Text: b[j]})
}
return capLines(out)
}
// addedDiff présente un contenu entièrement nouveau (création d'une page).
func addedDiff(text string) []DiffLine {
lines := splitLines(text)
out := make([]DiffLine, 0, len(lines))
for _, l := range lines {
out = append(out, DiffLine{Op: "+", Text: l})
}
return capLines(out)
}
func splitLines(s string) []string {
s = strings.ReplaceAll(s, "\r\n", "\n")
if s == "" {
return nil
}
return strings.Split(s, "\n")
}
// capLines borne la taille envoyée à l'UI et signale ce qui a été coupé.
func capLines(d []DiffLine) []DiffLine {
if len(d) <= diffMaxShown {
return d
}
cut := len(d) - diffMaxShown
out := append([]DiffLine{}, d[:diffMaxShown]...)
return append(out, DiffLine{Op: " ", Text: "…(" + itoa(cut) + " lignes de plus)"})
}
func itoa(n int) string {
if n == 0 {
return "0"
}
var b []byte
for n > 0 {
b = append([]byte{byte('0' + n%10)}, b...)
n /= 10
}
return string(b)
}