f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
183 lines
3.8 KiB
Go
183 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"go.kenn.io/agentsview/internal/config"
|
|
"go.kenn.io/agentsview/internal/db"
|
|
"go.kenn.io/agentsview/internal/importer"
|
|
)
|
|
|
|
type ImportConfig struct {
|
|
Type string
|
|
Path string
|
|
}
|
|
|
|
func runImport(cfg ImportConfig) {
|
|
appCfg, err := config.LoadMinimal()
|
|
if err != nil {
|
|
log.Fatalf("loading config: %v", err)
|
|
}
|
|
|
|
database, writeLock, err := openWriteDB(context.Background(), appCfg)
|
|
if err != nil {
|
|
log.Fatalf("Error opening database: %v", err)
|
|
}
|
|
defer closeWriteDB(database, writeLock)
|
|
|
|
ctx := context.Background()
|
|
|
|
// Handle zip files.
|
|
dir, cleanup, err := resolveImportSource(cfg.Path)
|
|
if err != nil {
|
|
log.Fatalf("Error: %v", err)
|
|
}
|
|
if cleanup != nil {
|
|
defer cleanup()
|
|
}
|
|
|
|
var stats importer.ImportStats
|
|
|
|
switch cfg.Type {
|
|
case "claude-ai":
|
|
stats, err = runClaudeAIImport(
|
|
ctx, database, dir, appCfg.LocalMachineName,
|
|
)
|
|
case "chatgpt":
|
|
assetsDir := filepath.Join(appCfg.DataDir, "assets")
|
|
stats, err = runChatGPTImport(
|
|
ctx, database, dir, assetsDir, appCfg.LocalMachineName,
|
|
)
|
|
default:
|
|
log.Fatalf(
|
|
"Unknown import type: %s (use claude-ai or chatgpt)",
|
|
cfg.Type,
|
|
)
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr)
|
|
log.Fatalf("Import failed: %v", err)
|
|
}
|
|
|
|
printImportSummary(stats)
|
|
|
|
if stats.Errors > 0 {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func runClaudeAIImport(
|
|
ctx context.Context, database *db.DB, path, machine string,
|
|
) (importer.ImportStats, error) {
|
|
jsonPath := path
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return importer.ImportStats{}, err
|
|
}
|
|
if info.IsDir() {
|
|
jsonPath = filepath.Join(path, "conversations.json")
|
|
}
|
|
|
|
f, err := os.Open(jsonPath)
|
|
if err != nil {
|
|
return importer.ImportStats{},
|
|
fmt.Errorf("opening %s: %w", jsonPath, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
return importer.ImportClaudeAI(
|
|
ctx, database, f, &importer.ImportCallbacks{
|
|
OnProgress: func(s importer.ImportStats) {
|
|
n := s.Imported + s.Updated + s.Skipped
|
|
fmt.Fprintf(
|
|
os.Stderr,
|
|
"\r%d conversations processed...", n,
|
|
)
|
|
},
|
|
OnIndexing: func() {
|
|
fmt.Fprintf(
|
|
os.Stderr,
|
|
"\rRebuilding search index... ",
|
|
)
|
|
},
|
|
}, machine,
|
|
)
|
|
}
|
|
|
|
func runChatGPTImport(
|
|
ctx context.Context, database *db.DB,
|
|
dir, assetsDir, machine string,
|
|
) (importer.ImportStats, error) {
|
|
return importer.ImportChatGPT(
|
|
ctx, database, dir, assetsDir,
|
|
&importer.ImportCallbacks{
|
|
OnProgress: func(s importer.ImportStats) {
|
|
n := s.Imported + s.Skipped
|
|
fmt.Fprintf(
|
|
os.Stderr,
|
|
"\r%d conversations processed...", n,
|
|
)
|
|
},
|
|
OnIndexing: func() {
|
|
fmt.Fprintf(
|
|
os.Stderr,
|
|
"\rRebuilding search index... ",
|
|
)
|
|
},
|
|
}, machine,
|
|
)
|
|
}
|
|
|
|
func printImportSummary(stats importer.ImportStats) {
|
|
total := stats.Imported + stats.Updated + stats.Skipped
|
|
fmt.Fprintf(os.Stderr, "\rDone: %d processed", total)
|
|
var parts []string
|
|
if stats.Imported > 0 {
|
|
parts = append(
|
|
parts, fmt.Sprintf("%d new", stats.Imported),
|
|
)
|
|
}
|
|
if stats.Updated > 0 {
|
|
parts = append(
|
|
parts, fmt.Sprintf("%d updated", stats.Updated),
|
|
)
|
|
}
|
|
if stats.Skipped > 0 {
|
|
parts = append(
|
|
parts, fmt.Sprintf("%d skipped", stats.Skipped),
|
|
)
|
|
}
|
|
if len(parts) > 0 {
|
|
fmt.Fprintf(
|
|
os.Stderr, " (%s)", strings.Join(parts, ", "),
|
|
)
|
|
}
|
|
fmt.Fprintln(os.Stderr)
|
|
if stats.Errors > 0 {
|
|
fmt.Fprintf(os.Stderr, " %d errors\n", stats.Errors)
|
|
}
|
|
}
|
|
|
|
// resolveImportSource handles zip extraction. If the path is
|
|
// a .zip file, it extracts to a temp dir and returns the dir
|
|
// path with a cleanup function. Otherwise returns the original
|
|
// path with nil cleanup.
|
|
func resolveImportSource(
|
|
path string,
|
|
) (string, func(), error) {
|
|
if strings.HasSuffix(strings.ToLower(path), ".zip") {
|
|
return importer.ExtractZip(path)
|
|
}
|
|
if _, err := os.Stat(path); err != nil {
|
|
return "", nil,
|
|
fmt.Errorf("cannot access %s: %w", path, err)
|
|
}
|
|
return path, nil, nil
|
|
}
|