perf: optimize hot path & reorganize project config
Backend (app.go): - AICache: replace linear scan with map-based O(1) lookup (get/getRow/put/putRow) - runMatchOnData: pre-compute B-column cleaned values, parsed times, extract values to eliminate O(n*m) regex/time-parse from inner loop - calcSimilarity: eliminate double rune conversion (levenshteinDistance now takes []rune) - Add similarityFromCleaned to skip redundant regex step in hot path - Fix corrupted bare 'n' literal causing build failure - Move saveToFile out of inner match loop (was called per item) - dataMu: Mutex -> RWMutex (exportHeaders/ExportResults use RLock) - buildGenericAIPrompt: fix truncation check order (check after write) Project: - .gitignore: deduplicate & tighten rules; track package-lock.json and .vscode/* - Clean up stale root binary (data-matcher.exe)
This commit is contained in:
37
.gitignore
vendored
37
.gitignore
vendored
@@ -1,32 +1,27 @@
|
||||
# --- Build output ---
|
||||
build/bin
|
||||
build/*.exe
|
||||
build/*.app
|
||||
# === 构建产物 ===
|
||||
build/
|
||||
|
||||
# --- Frontend ---
|
||||
node_modules/
|
||||
frontend/dist
|
||||
frontend/node_modules/
|
||||
frontend/package-lock.json
|
||||
|
||||
# --- Go ---
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
# === Go ===
|
||||
vendor/
|
||||
*.test
|
||||
*.out
|
||||
vendor/
|
||||
|
||||
# --- IDE ---
|
||||
# === IDE / 编辑器 ===
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# === 操作系统 ===
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# VS Code(保留 .vscode/extensions.json 以便共享推荐)
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
# === 前端 ===
|
||||
frontend/node_modules/
|
||||
frontend/dist/
|
||||
|
||||
# === Wails 运行时(开发模式临时文件)===
|
||||
# 注意:frontend/wailsjs/ 是 Wails 自动生成的绑定代码,必须提交
|
||||
|
||||
# === AI 缓存(本地临时文件,不提交)===
|
||||
data-matcher-ai-cache.json
|
||||
|
||||
21
.vscode/settings.json
vendored
Normal file
21
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.tabSize": 2,
|
||||
"files.eol": "\n",
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[go]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": false,
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"frontend/dist": true,
|
||||
"build": true,
|
||||
"node_modules": true
|
||||
}
|
||||
}
|
||||
13
.vscode/tasks.json
vendored
Normal file
13
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "dev (wails dev)",
|
||||
"type": "shell",
|
||||
"command": "wails dev",
|
||||
"isBackground": true,
|
||||
"problemMatcher": [],
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
543
app.go
543
app.go
@@ -27,20 +27,12 @@ import (
|
||||
|
||||
// MatchResult 匹配结果
|
||||
type MatchResult struct {
|
||||
// 新字段(通用化)
|
||||
RowAData []string `json:"rowAData"` // A 表原始所有列(新)
|
||||
RowBKey string `json:"rowBKey"` // B 表匹配列的值(新)
|
||||
ExtractValue string `json:"extractValue"` // 从 B 表提取的目标列值(新)
|
||||
|
||||
// 旧字段(向后兼容)
|
||||
MonthlyCellName string `json:"monthlyCellName"`
|
||||
DailyCellID string `json:"dailyCellId"`
|
||||
InterruptReason string `json:"interruptReason"`
|
||||
|
||||
// 公共字段
|
||||
TimeDiff string `json:"timeDiff"`
|
||||
RowAData []string `json:"rowAData"` // A 表原始所有列
|
||||
RowBKey string `json:"rowBKey"` // B 表匹配列的值
|
||||
ExtractValue string `json:"extractValue"` // 从 B 表提取的目标列值
|
||||
TimeDiff string `json:"timeDiff"`
|
||||
SimilarityScore float64 `json:"similarityScore"`
|
||||
AIMatched bool `json:"aiMatched"`
|
||||
AIMatched bool `json:"aiMatched"`
|
||||
}
|
||||
|
||||
// ProgressPayload 进度信息
|
||||
@@ -113,19 +105,30 @@ type AICacheInfo struct {
|
||||
FilePath string `json:"filePath"`
|
||||
}
|
||||
|
||||
// AICacheEntry 单条缓存记录
|
||||
// AICacheEntry 单条缓存记录(批量 prompt → 响应)
|
||||
type AICacheEntry struct {
|
||||
PromptHash string `json:"promptHash"`
|
||||
Response string `json:"response"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
}
|
||||
|
||||
// AIRowCacheEntry 单行 AI 匹配缓存(跨批次复用)
|
||||
type AIRowCacheEntry struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"` // AI 匹配到的 extractValue
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
}
|
||||
|
||||
// AICache AI 响应缓存(持久化到临时文件)
|
||||
type AICache struct {
|
||||
Entries []AICacheEntry `json:"entries"`
|
||||
mu sync.RWMutex // 小写,必须保持非导出以兼容 JSON 序列化
|
||||
filePath string
|
||||
maxSize int // 最大缓存条目数
|
||||
Entries []AICacheEntry `json:"entries"`
|
||||
RowEntries []AIRowCacheEntry `json:"rowEntries"`
|
||||
entriesIdx map[string]int // promptHash → index in Entries (O(1) lookup)
|
||||
rowEntriesIdx map[string]int // key → index in RowEntries (O(1) lookup)
|
||||
mu sync.RWMutex
|
||||
filePath string
|
||||
maxSize int // 批量缓存最大条目数
|
||||
maxRowSize int // 行级缓存最大条目数
|
||||
}
|
||||
|
||||
// cacheFileName 缓存文件名
|
||||
@@ -139,13 +142,26 @@ const (
|
||||
defaultMaxPreview = 3
|
||||
defaultMaxBNoTime = 200
|
||||
defaultAIWindowPadH = 3.0
|
||||
maxPromptBChars = 80000 // B 表数据在 prompt 中的最大字符数
|
||||
)
|
||||
// rebuildIndexes 从切片重建索引 map(反序列化或裁剪后调用)
|
||||
func (c *AICache) rebuildIndexes() {
|
||||
c.entriesIdx = make(map[string]int, len(c.Entries))
|
||||
for i := range c.Entries {
|
||||
c.entriesIdx[c.Entries[i].PromptHash] = i
|
||||
}
|
||||
c.rowEntriesIdx = make(map[string]int, len(c.RowEntries))
|
||||
for i := range c.RowEntries {
|
||||
c.rowEntriesIdx[c.RowEntries[i].Key] = i
|
||||
}
|
||||
}
|
||||
|
||||
// newAICache 创建缓存实例并加载已有数据
|
||||
func newAICache() *AICache {
|
||||
c := &AICache{
|
||||
filePath: filepath.Join(os.TempDir(), cacheFileName),
|
||||
maxSize: 500,
|
||||
filePath: filepath.Join(os.TempDir(), cacheFileName),
|
||||
maxSize: 500,
|
||||
maxRowSize: 5000,
|
||||
}
|
||||
c.loadFromFile()
|
||||
return c
|
||||
@@ -155,11 +171,20 @@ func newAICache() *AICache {
|
||||
func (c *AICache) loadFromFile() {
|
||||
data, err := os.ReadFile(c.filePath)
|
||||
if err != nil {
|
||||
return // 文件不存在或无法读取,从空缓存开始
|
||||
// 文件不存在或无法读取,从空缓存开始
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Printf("[CACHE] 读取缓存文件失败: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
_ = json.Unmarshal(data, c) // 忽略解析错误,重置为 entries
|
||||
if err := json.Unmarshal(data, c); err != nil {
|
||||
fmt.Printf("[CACHE] 解析缓存文件失败,重置为空: %v\n", err)
|
||||
c.Entries = nil
|
||||
c.RowEntries = nil
|
||||
}
|
||||
c.rebuildIndexes()
|
||||
}
|
||||
|
||||
// saveToFile 将缓存写入磁盘
|
||||
@@ -168,19 +193,20 @@ func (c *AICache) saveToFile() {
|
||||
data, err := json.Marshal(c)
|
||||
c.mu.RUnlock()
|
||||
if err != nil {
|
||||
fmt.Printf("[CACHE] 序列化缓存失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
_ = os.WriteFile(c.filePath, data, 0600)
|
||||
if err := os.WriteFile(c.filePath, data, 0600); err != nil {
|
||||
fmt.Printf("[CACHE] 写入缓存文件失败: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// get 根据 hash 查找缓存,命中返回响应,否则返回空
|
||||
func (c *AICache) get(hash string) (string, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
for i := range c.Entries {
|
||||
if c.Entries[i].PromptHash == hash {
|
||||
return c.Entries[i].Response, true
|
||||
}
|
||||
if idx, ok := c.entriesIdx[hash]; ok && idx < len(c.Entries) && c.Entries[idx].PromptHash == hash {
|
||||
return c.Entries[idx].Response, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -191,27 +217,28 @@ func (c *AICache) put(hash, response string) {
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// 去重:如果已存在则覆盖
|
||||
for i := range c.Entries {
|
||||
if c.Entries[i].PromptHash == hash {
|
||||
c.Entries[i].Response = response
|
||||
c.Entries[i].CreatedAt = time.Now().Unix()
|
||||
return
|
||||
}
|
||||
if idx, ok := c.entriesIdx[hash]; ok && idx < len(c.Entries) && c.Entries[idx].PromptHash == hash {
|
||||
c.Entries[idx].Response = response
|
||||
c.Entries[idx].CreatedAt = time.Now().Unix()
|
||||
return
|
||||
}
|
||||
|
||||
// 新增条目
|
||||
idx := len(c.Entries)
|
||||
c.Entries = append(c.Entries, AICacheEntry{
|
||||
PromptHash: hash,
|
||||
Response: response,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
})
|
||||
c.entriesIdx[hash] = idx
|
||||
|
||||
// 超过上限则删除最旧的条目
|
||||
if len(c.Entries) > c.maxSize {
|
||||
// 按 CreatedAt 排序保留最新的
|
||||
sort.Slice(c.Entries, func(i, j int) bool {
|
||||
return c.Entries[i].CreatedAt > c.Entries[j].CreatedAt
|
||||
})
|
||||
c.Entries = c.Entries[:c.maxSize]
|
||||
c.rebuildIndexes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +247,9 @@ func (c *AICache) clear() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.Entries = nil
|
||||
c.RowEntries = nil
|
||||
c.entriesIdx = nil
|
||||
c.rowEntriesIdx = nil
|
||||
_ = os.Remove(c.filePath)
|
||||
}
|
||||
|
||||
@@ -227,7 +257,66 @@ func (c *AICache) clear() {
|
||||
func (c *AICache) stat() (count int, path string) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return len(c.Entries), c.filePath
|
||||
return len(c.Entries) + len(c.RowEntries), c.filePath
|
||||
}
|
||||
|
||||
// rowKey 为单行匹配构建缓存键
|
||||
func (a *App) buildRowCacheKey(matchValue, timeStr string, config MatchConfig) string {
|
||||
parts := fmt.Sprintf("%s|%s|%s|%.1f|%s",
|
||||
matchValue, timeStr, config.RegexPattern, config.TimeWindow,
|
||||
filepath.Base(config.FileBPath))
|
||||
h := sha256.Sum256([]byte(parts))
|
||||
return hex.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
// getRow 查找行级缓存
|
||||
func (c *AICache) getRow(key string) (string, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if idx, ok := c.rowEntriesIdx[key]; ok && idx < len(c.RowEntries) && c.RowEntries[idx].Key == key {
|
||||
return c.RowEntries[idx].Value, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// putRow 存入行级缓存(线程安全 + 自动裁剪)
|
||||
func (c *AICache) putRow(key, value string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// 去重:更新已存在的条目
|
||||
if idx, ok := c.rowEntriesIdx[key]; ok && idx < len(c.RowEntries) && c.RowEntries[idx].Key == key {
|
||||
c.RowEntries[idx].Value = value
|
||||
c.RowEntries[idx].CreatedAt = time.Now().Unix()
|
||||
return
|
||||
}
|
||||
|
||||
// 新增条目
|
||||
idx := len(c.RowEntries)
|
||||
c.RowEntries = append(c.RowEntries, AIRowCacheEntry{
|
||||
Key: key,
|
||||
Value: value,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
})
|
||||
c.rowEntriesIdx[key] = idx
|
||||
|
||||
// 超过上限则删除最旧的条目
|
||||
if len(c.RowEntries) > c.maxRowSize {
|
||||
sort.Slice(c.RowEntries, func(i, j int) bool {
|
||||
return c.RowEntries[i].CreatedAt > c.RowEntries[j].CreatedAt
|
||||
})
|
||||
c.RowEntries = c.RowEntries[:c.maxRowSize]
|
||||
c.rebuildIndexes()
|
||||
}
|
||||
}
|
||||
|
||||
// matchPrep 匹配准备的中间结果
|
||||
type matchPrep struct {
|
||||
dataA, dataB [][]string
|
||||
reg *regexp.Regexp
|
||||
timeWindow float64
|
||||
threshold float64
|
||||
windowDuration time.Duration
|
||||
}
|
||||
|
||||
// ---------- App 结构体 ----------
|
||||
@@ -238,6 +327,7 @@ type App struct {
|
||||
aiCache *AICache
|
||||
|
||||
// 最近一次匹配的配置和表头(供导出使用)
|
||||
dataMu sync.RWMutex
|
||||
lastConfig MatchConfig
|
||||
headersA []string
|
||||
headersB []string
|
||||
@@ -411,9 +501,7 @@ func parseTimeFlexible(timeStr string) (time.Time, error) {
|
||||
|
||||
// ---------- Levenshtein 距离算法 ----------
|
||||
|
||||
func levenshteinDistance(s1, s2 string) int {
|
||||
runes1 := []rune(s1)
|
||||
runes2 := []rune(s2)
|
||||
func levenshteinDistance(runes1, runes2 []rune) int {
|
||||
m, n := len(runes1), len(runes2)
|
||||
|
||||
// 使用一维数组优化空间复杂度
|
||||
@@ -438,12 +526,6 @@ func levenshteinDistance(s1, s2 string) int {
|
||||
return dp[n]
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// CalculateSimilarity 计算清洗后中文名称的相似度(基于 Levenshtein 距离归一化)
|
||||
func (a *App) CalculateSimilarity(s1, s2 string) float64 {
|
||||
@@ -473,7 +555,7 @@ func calcSimilarity(s1, s2 string, reg *regexp.Regexp, caseSensitive bool) float
|
||||
return 0.0
|
||||
}
|
||||
|
||||
dist := levenshteinDistance(clean1, clean2)
|
||||
dist := levenshteinDistance(r1, r2)
|
||||
maxLen := math.Max(float64(len(r1)), float64(len(r2)))
|
||||
return 1.0 - float64(dist)/maxLen
|
||||
}
|
||||
@@ -485,6 +567,24 @@ func cleanWithRegex(input string, reg *regexp.Regexp) string {
|
||||
}
|
||||
return reg.ReplaceAllString(input, "")
|
||||
}
|
||||
// similarityFromCleaned 基于已清洗字符串计算相似度(跳过 regex 步骤,避免重复清洗)
|
||||
func similarityFromCleaned(clean1, clean2 string, caseSensitive bool) float64 {
|
||||
if !caseSensitive {
|
||||
clean1 = strings.ToLower(clean1)
|
||||
clean2 = strings.ToLower(clean2)
|
||||
}
|
||||
r1 := []rune(clean1)
|
||||
r2 := []rune(clean2)
|
||||
if len(r1) == 0 && len(r2) == 0 {
|
||||
return 1.0
|
||||
}
|
||||
if len(r1) == 0 || len(r2) == 0 {
|
||||
return 0.0
|
||||
}
|
||||
dist := levenshteinDistance(r1, r2)
|
||||
maxLen := math.Max(float64(len(r1)), float64(len(r2)))
|
||||
return 1.0 - float64(dist)/maxLen
|
||||
}
|
||||
|
||||
// ---------- 文件读取(通用)----------
|
||||
|
||||
@@ -551,25 +651,22 @@ func getCell(row []string, idx int) string {
|
||||
}
|
||||
|
||||
|
||||
// RunMatch 接收完整 MatchConfig,按列索引执行通用匹配
|
||||
func (a *App) RunMatch(config MatchConfig) ([]MatchResult, error) {
|
||||
// 1. 编译正则
|
||||
// prepareMatch 编译正则、读取文件、初始化默认值(RunMatch / RunMatchWithAI 共用)
|
||||
func (a *App) prepareMatch(config MatchConfig) (*matchPrep, error) {
|
||||
reg, err := compileRegex(config.RegexPattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 默认值兜底
|
||||
timeWindow := config.TimeWindow
|
||||
if timeWindow <= 0 {
|
||||
timeWindow = defaultTimeWindowH
|
||||
tw := config.TimeWindow
|
||||
if tw <= 0 {
|
||||
tw = defaultTimeWindowH
|
||||
}
|
||||
threshold := config.Threshold
|
||||
if threshold <= 0 {
|
||||
threshold = defaultThreshold
|
||||
th := config.Threshold
|
||||
if th <= 0 {
|
||||
th = defaultThreshold
|
||||
}
|
||||
|
||||
// 3. 读取原始数据
|
||||
a.emitProgress(0, 100, "正在读取 A 表...", "reading")
|
||||
rowsA, err := a.readRawRows(config.FileAPath)
|
||||
if err != nil {
|
||||
@@ -587,22 +684,35 @@ func (a *App) RunMatch(config MatchConfig) ([]MatchResult, error) {
|
||||
return nil, fmt.Errorf("B 表无有效数据行")
|
||||
}
|
||||
|
||||
// 保存表头供导出使用
|
||||
a.dataMu.Lock()
|
||||
a.headersA = rowsA[0]
|
||||
a.headersB = rowsB[0]
|
||||
a.lastConfig = config
|
||||
a.dataMu.Unlock()
|
||||
|
||||
dataA := rowsA[1:]
|
||||
dataB := rowsB[1:]
|
||||
windowDuration := time.Duration(timeWindow * float64(time.Hour))
|
||||
|
||||
return a.runMatchOnData(dataA, dataB, reg, config, timeWindow, threshold, windowDuration)
|
||||
return &matchPrep{
|
||||
dataA: rowsA[1:],
|
||||
dataB: rowsB[1:],
|
||||
reg: reg,
|
||||
timeWindow: tw,
|
||||
threshold: th,
|
||||
windowDuration: time.Duration(tw * float64(time.Hour)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// runMatchOnData 在已读取的数据上执行匹配(内部使用,避免重复 I/O)
|
||||
func (a *App) runMatchOnData(dataA, dataB [][]string, reg *regexp.Regexp, config MatchConfig, _, threshold float64, windowDuration time.Duration) ([]MatchResult, error) {
|
||||
// RunMatch 接收完整 MatchConfig,按列索引执行通用匹配
|
||||
func (a *App) RunMatch(config MatchConfig) ([]MatchResult, error) {
|
||||
prep, err := a.prepareMatch(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.runMatchOnData(prep, config)
|
||||
}
|
||||
|
||||
// runMatchOnData 在已读取的数据上执行匹配
|
||||
func (a *App) runMatchOnData(prep *matchPrep, config MatchConfig) ([]MatchResult, error) {
|
||||
useTime := config.ColATimeIndex >= 0 && config.ColBTimeIndex >= 0
|
||||
totalA := len(dataA)
|
||||
totalA := len(prep.dataA)
|
||||
var results []MatchResult
|
||||
|
||||
useAllMatches := config.AllMatches
|
||||
@@ -611,7 +721,40 @@ func (a *App) runMatchOnData(dataA, dataB [][]string, reg *regexp.Regexp, config
|
||||
maxPreview = defaultMaxPreview
|
||||
}
|
||||
|
||||
for i, rowA := range dataA {
|
||||
// 预计算 B 表清洗后的匹配值,避免内层循环中重复 regex 替换(O(n*m)→O(n+m))
|
||||
totalB := len(prep.dataB)
|
||||
cleanedBMatch := make([]string, totalB)
|
||||
origBMatch := make([]string, totalB)
|
||||
parsedBTime := make([]time.Time, totalB)
|
||||
hasBTime := make([]bool, totalB)
|
||||
bExtractVal := make([]string, totalB)
|
||||
for bIdx, rowB := range prep.dataB {
|
||||
matchStrB := getCell(rowB, config.ColBMatchIndex)
|
||||
origBMatch[bIdx] = matchStrB
|
||||
if matchStrB == "" {
|
||||
cleanedBMatch[bIdx] = ""
|
||||
} else {
|
||||
cleanedBMatch[bIdx] = cleanWithRegex(matchStrB, prep.reg)
|
||||
}
|
||||
if useTime {
|
||||
t, err := parseTimeFlexible(getCell(rowB, config.ColBTimeIndex))
|
||||
if err == nil {
|
||||
parsedBTime[bIdx] = t
|
||||
hasBTime[bIdx] = true
|
||||
}
|
||||
}
|
||||
bExtractVal[bIdx] = getCell(rowB, config.ColBExtractIndex)
|
||||
}
|
||||
for i, rowA := range prep.dataA {
|
||||
// 定期检查是否取消
|
||||
if a.ctx != nil {
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return results, a.ctx.Err()
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if i%10 == 0 || i == totalA-1 {
|
||||
pct := (i + 1) * 100 / totalA
|
||||
a.emitProgress(i+1, totalA,
|
||||
@@ -633,46 +776,46 @@ func (a *App) runMatchOnData(dataA, dataB [][]string, reg *regexp.Regexp, config
|
||||
}
|
||||
}
|
||||
|
||||
cleanA := cleanWithRegex(matchStrA, reg)
|
||||
cleanA := cleanWithRegex(matchStrA, prep.reg)
|
||||
// 收集该 A 行的所有候选匹配
|
||||
var candidates []MatchResult
|
||||
|
||||
for _, rowB := range dataB {
|
||||
matchStrB := getCell(rowB, config.ColBMatchIndex)
|
||||
if matchStrB == "" {
|
||||
continue
|
||||
}
|
||||
for bIdx := range prep.dataB {
|
||||
matchStrB := origBMatch[bIdx]
|
||||
if matchStrB == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var timeDiff time.Duration
|
||||
if hasTimeA && useTime {
|
||||
tB, err := parseTimeFlexible(getCell(rowB, config.ColBTimeIndex))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
td := timeA.Sub(tB)
|
||||
if td < -windowDuration || td > windowDuration {
|
||||
continue
|
||||
}
|
||||
timeDiff = td
|
||||
}
|
||||
var timeDiff time.Duration
|
||||
if hasTimeA && useTime {
|
||||
if !hasBTime[bIdx] {
|
||||
continue
|
||||
}
|
||||
tB := parsedBTime[bIdx]
|
||||
td := timeA.Sub(tB)
|
||||
if td < -prep.windowDuration || td > prep.windowDuration {
|
||||
continue
|
||||
}
|
||||
timeDiff = td
|
||||
}
|
||||
|
||||
cleanB := cleanWithRegex(matchStrB, reg)
|
||||
if cleanA == "" || cleanB == "" {
|
||||
continue
|
||||
}
|
||||
cleanB := cleanedBMatch[bIdx]
|
||||
if cleanA == "" || cleanB == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
similarity := calcSimilarity(matchStrA, matchStrB, reg, config.CaseSensitive)
|
||||
similarity := similarityFromCleaned(cleanA, cleanB, config.CaseSensitive)
|
||||
|
||||
if i < maxPreview {
|
||||
fmt.Printf("[DEBUG] | A[%d]='%s'→'%s' | B='%s'→'%s' | 相似度=%.4f\n",
|
||||
i, matchStrA, cleanA, matchStrB, cleanB, similarity)
|
||||
}
|
||||
|
||||
if similarity >= threshold {
|
||||
if similarity >= prep.threshold {
|
||||
mr := MatchResult{
|
||||
RowAData: rowA,
|
||||
RowBKey: matchStrB,
|
||||
ExtractValue: getCell(rowB, config.ColBExtractIndex),
|
||||
ExtractValue: bExtractVal[bIdx],
|
||||
TimeDiff: formatTimeDiff(timeDiff),
|
||||
SimilarityScore: math.Round(similarity*10000) / 10000,
|
||||
AIMatched: false,
|
||||
@@ -757,60 +900,25 @@ func (a *App) RunMatchWithAI(config MatchConfig) ([]MatchResult, error) {
|
||||
return nil, fmt.Errorf("请先设置 Deepseek API 密钥")
|
||||
}
|
||||
|
||||
// 1. 编译正则
|
||||
reg, err := compileRegex(config.RegexPattern)
|
||||
prep, err := a.prepareMatch(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 默认值兜底
|
||||
timeWindow := config.TimeWindow
|
||||
if timeWindow <= 0 {
|
||||
timeWindow = defaultTimeWindowH
|
||||
}
|
||||
threshold := config.Threshold
|
||||
if threshold <= 0 {
|
||||
threshold = defaultThreshold
|
||||
}
|
||||
windowDuration := time.Duration(timeWindow * float64(time.Hour))
|
||||
|
||||
// 3. 一次读取数据,供匹配和 AI 使用
|
||||
a.emitProgress(0, 100, "正在读取 A 表...", "reading")
|
||||
rowsA, err := a.readRawRows(config.FileAPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取 A 表失败: %v", err)
|
||||
}
|
||||
a.emitProgress(0, 100, "正在读取 B 表...", "reading")
|
||||
rowsB, err := a.readRawRows(config.FileBPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取 B 表失败: %v", err)
|
||||
}
|
||||
if len(rowsA) < 2 || len(rowsB) < 2 {
|
||||
return nil, fmt.Errorf("数据表无有效数据行")
|
||||
}
|
||||
|
||||
// 保存表头供导出使用
|
||||
a.headersA = rowsA[0]
|
||||
a.headersB = rowsB[0]
|
||||
a.lastConfig = config
|
||||
|
||||
dataA := rowsA[1:]
|
||||
dataB := rowsB[1:]
|
||||
|
||||
// 4. 先执行基础匹配(使用已读取的数据,避免重复 I/O)
|
||||
results, err := a.runMatchOnData(dataA, dataB, reg, config, timeWindow, threshold, windowDuration)
|
||||
// 1. 先执行基础匹配
|
||||
results, err := a.runMatchOnData(prep, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. 找出未被基础匹配覆盖的 A 表行
|
||||
// 2. 找出未被基础匹配覆盖的 A 表行
|
||||
matchedSet := make(map[string]bool)
|
||||
for _, r := range results {
|
||||
matchedSet[strings.Join(r.RowAData, "\x00")] = true
|
||||
}
|
||||
|
||||
var unmatchedA [][]string
|
||||
for _, row := range dataA {
|
||||
for _, row := range prep.dataA {
|
||||
if !matchedSet[strings.Join(row, "\x00")] {
|
||||
unmatchedA = append(unmatchedA, row)
|
||||
}
|
||||
@@ -821,26 +929,61 @@ func (a *App) RunMatchWithAI(config MatchConfig) ([]MatchResult, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// 6. AI 增强匹配
|
||||
// 3. AI 增强匹配(先查行级缓存,减少 API 调用)
|
||||
useTime := config.ColATimeIndex >= 0 && config.ColBTimeIndex >= 0
|
||||
totalUnmatched := len(unmatchedA)
|
||||
a.emitProgress(0, totalUnmatched,
|
||||
fmt.Sprintf("AI 增强匹配:还有 %d 条未匹配记录,正在调用 Deepseek...", totalUnmatched),
|
||||
"ai-enhancing")
|
||||
|
||||
aiMatched := 0
|
||||
var failedBatches []int
|
||||
|
||||
// 3a. 检查行级缓存,命中则直接加入结果
|
||||
var uncachedA [][]string
|
||||
cacheHits := 0
|
||||
for _, row := range unmatchedA {
|
||||
matchVal := getCell(row, config.ColAMatchIndex)
|
||||
timeStr := ""
|
||||
if useTime {
|
||||
timeStr = getCell(row, config.ColATimeIndex)
|
||||
}
|
||||
cacheKey := a.buildRowCacheKey(matchVal, timeStr, config)
|
||||
if cachedVal, ok := a.aiCache.getRow(cacheKey); ok {
|
||||
results = append(results, MatchResult{
|
||||
RowAData: row,
|
||||
RowBKey: "",
|
||||
ExtractValue: cachedVal,
|
||||
SimilarityScore: 0,
|
||||
AIMatched: true,
|
||||
})
|
||||
aiMatched++
|
||||
cacheHits++
|
||||
} else {
|
||||
uncachedA = append(uncachedA, row)
|
||||
}
|
||||
}
|
||||
|
||||
if cacheHits > 0 {
|
||||
fmt.Printf("[CACHE] ✓ 行级缓存命中 %d 条,剩余 %d 条需 AI 处理\n", cacheHits, len(uncachedA))
|
||||
}
|
||||
|
||||
if len(uncachedA) == 0 {
|
||||
a.emitProgress(1, 1,
|
||||
fmt.Sprintf("AI 增强完成!全部 %d 条命中缓存", cacheHits), "done")
|
||||
return results, nil
|
||||
}
|
||||
|
||||
totalUnmatched := len(uncachedA)
|
||||
a.emitProgress(0, totalUnmatched,
|
||||
fmt.Sprintf("AI 增强匹配:%d 条命中缓存,%d 条需调用 Deepseek...", cacheHits, totalUnmatched),
|
||||
"ai-enhancing")
|
||||
|
||||
for batchStart := 0; batchStart < totalUnmatched; batchStart += defaultBatchSize {
|
||||
end := batchStart + defaultBatchSize
|
||||
if end > totalUnmatched {
|
||||
end = totalUnmatched
|
||||
}
|
||||
end := min(batchStart+defaultBatchSize, totalUnmatched)
|
||||
batchNum := (batchStart / defaultBatchSize) + 1
|
||||
|
||||
a.emitProgress(batchStart+1, totalUnmatched,
|
||||
fmt.Sprintf("AI 分析中 %d/%d (第 %d 批)...", end, totalUnmatched, (batchStart/defaultBatchSize)+1),
|
||||
fmt.Sprintf("AI 分析中 %d/%d (第 %d 批)...", end, totalUnmatched, batchNum),
|
||||
"ai-enhancing")
|
||||
|
||||
batch := unmatchedA[batchStart:end]
|
||||
batch := uncachedA[batchStart:end]
|
||||
|
||||
// 计算本批 A 表的时间范围
|
||||
var minTime, maxTime time.Time
|
||||
@@ -868,10 +1011,10 @@ func (a *App) RunMatchWithAI(config MatchConfig) ([]MatchResult, error) {
|
||||
// 过滤 B 表在时间窗口内的行(用户配置时间窗口 + 额外余量覆盖批次跨度)
|
||||
var relevantB [][]string
|
||||
if hasBatchTime && useTime {
|
||||
padding := windowDuration + time.Duration(defaultAIWindowPadH)*time.Hour
|
||||
padding := prep.windowDuration + time.Duration(defaultAIWindowPadH)*time.Hour
|
||||
ws := minTime.Add(-padding)
|
||||
we := maxTime.Add(padding)
|
||||
for _, row := range dataB {
|
||||
for _, row := range prep.dataB {
|
||||
t, err := parseTimeFlexible(getCell(row, config.ColBTimeIndex))
|
||||
if err != nil || t.Before(ws) || t.After(we) {
|
||||
continue
|
||||
@@ -880,17 +1023,16 @@ func (a *App) RunMatchWithAI(config MatchConfig) ([]MatchResult, error) {
|
||||
}
|
||||
} else {
|
||||
// 无时间列时限制 B 表条数以控制 token 消耗
|
||||
maxB := defaultMaxBNoTime
|
||||
if len(dataB) < maxB {
|
||||
maxB = len(dataB)
|
||||
}
|
||||
relevantB = dataB[:maxB]
|
||||
maxB := min(defaultMaxBNoTime, len(prep.dataB))
|
||||
relevantB = prep.dataB[:maxB]
|
||||
}
|
||||
|
||||
// 构建 AI 提示
|
||||
prompt := a.buildGenericAIPrompt(batch, relevantB, config, windowDuration, hasBatchTime)
|
||||
prompt := a.buildGenericAIPrompt(batch, relevantB, config, prep.windowDuration, hasBatchTime)
|
||||
aiResp, err := a.callDeepseekAPI(prompt)
|
||||
if err != nil {
|
||||
fmt.Printf("[AI-WARN] 第 %d 批 API 调用失败: %v\n", batchNum, err)
|
||||
failedBatches = append(failedBatches, batchNum)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -911,7 +1053,8 @@ func (a *App) RunMatchWithAI(config MatchConfig) ([]MatchResult, error) {
|
||||
}
|
||||
if parseErr != nil {
|
||||
fmt.Printf("[AI-WARN] 响应解析失败 (第 %d 批): %s\n 原始响应: %.200s\n",
|
||||
(batchStart/defaultBatchSize)+1, parseErr.Error(), aiResp)
|
||||
batchNum, parseErr.Error(), aiResp)
|
||||
failedBatches = append(failedBatches, batchNum)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -930,13 +1073,27 @@ func (a *App) RunMatchWithAI(config MatchConfig) ([]MatchResult, error) {
|
||||
AIMatched: true,
|
||||
}
|
||||
results = append(results, mr)
|
||||
aiMatched++
|
||||
aiMatched++
|
||||
|
||||
// 写入行级缓存
|
||||
matchVal := getCell(rowA, config.ColAMatchIndex)
|
||||
timeStr := ""
|
||||
if useTime {
|
||||
timeStr = getCell(rowA, config.ColATimeIndex)
|
||||
}
|
||||
cacheKey := a.buildRowCacheKey(matchVal, timeStr, config)
|
||||
a.aiCache.putRow(cacheKey, val)
|
||||
}
|
||||
}
|
||||
a.aiCache.saveToFile()
|
||||
|
||||
a.emitProgress(totalUnmatched, totalUnmatched,
|
||||
fmt.Sprintf("AI 增强完成!基础匹配 %d 条 + AI 补充 %d 条 = 共 %d 条",
|
||||
len(results)-aiMatched, aiMatched, len(results)), "done")
|
||||
// 构建完成消息
|
||||
msg := fmt.Sprintf("AI 增强完成!基础匹配 %d 条 + AI 补充 %d 条 = 共 %d 条",
|
||||
len(results)-aiMatched, aiMatched, len(results))
|
||||
if len(failedBatches) > 0 {
|
||||
msg += fmt.Sprintf("(警告:第 %v 批失败)", failedBatches)
|
||||
}
|
||||
a.emitProgress(totalUnmatched, totalUnmatched, msg, "done")
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -967,7 +1124,8 @@ func (a *App) buildGenericAIPrompt(unmatched, bRows [][]string, config MatchConf
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("\nB 表参考数据(共 %d 条):\n", len(bRows)))
|
||||
for _, row := range bRows {
|
||||
truncated := false
|
||||
for i, row := range bRows {
|
||||
matchVal := getCell(row, config.ColBMatchIndex)
|
||||
extractVal := getCell(row, config.ColBExtractIndex)
|
||||
sb.WriteString(fmt.Sprintf(" 「%s」 → 目标列值: 「%s」", matchVal, extractVal))
|
||||
@@ -975,6 +1133,15 @@ func (a *App) buildGenericAIPrompt(unmatched, bRows [][]string, config MatchConf
|
||||
sb.WriteString(fmt.Sprintf(", 时间=%s", getCell(row, config.ColBTimeIndex)))
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
// 限制 B 表部分总字符数,防止 prompt 超出 token 限制
|
||||
if sb.Len() > maxPromptBChars {
|
||||
fmt.Printf("[AI-WARN] Prompt B 表数据超长 (%d 条,%d 字符),截断于第 %d 条\n", len(bRows), sb.Len(), i)
|
||||
truncated = true
|
||||
}
|
||||
if truncated {
|
||||
sb.WriteString(fmt.Sprintf(" ... 已截断,省略 %d 条\n", len(bRows)-i-1))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString("\n请返回 JSON 格式的匹配结果。")
|
||||
@@ -1037,7 +1204,10 @@ func (a *App) callDeepseekAPI(messages []deepseekMessage) (string, error) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, _ := io.ReadAll(resp.Body)
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取 Deepseek 响应失败: %v", err)
|
||||
}
|
||||
var dr deepseekResponse
|
||||
if err := json.Unmarshal(respBytes, &dr); err != nil {
|
||||
return "", fmt.Errorf("解析 Deepseek 响应失败: %v", err)
|
||||
@@ -1091,7 +1261,12 @@ func (a *App) ExportResults(results []MatchResult) (string, error) {
|
||||
return "", fmt.Errorf("没有匹配结果可以导出")
|
||||
}
|
||||
|
||||
isCSV := a.lastConfig.ExportFormat == "csv"
|
||||
a.dataMu.RLock()
|
||||
useCSV := a.lastConfig.ExportFormat == "csv"
|
||||
includeHdr := a.lastConfig.IncludeHeader
|
||||
a.dataMu.RUnlock()
|
||||
|
||||
isCSV := useCSV
|
||||
ext := ".xlsx"
|
||||
filterDisplay := "Excel 文件 (*.xlsx)"
|
||||
filterPattern := "*.xlsx"
|
||||
@@ -1119,16 +1294,21 @@ func (a *App) ExportResults(results []MatchResult) (string, error) {
|
||||
}
|
||||
|
||||
if isCSV {
|
||||
return a.exportResultsCSV(results, savePath)
|
||||
return a.exportResultsCSV(results, savePath, includeHdr)
|
||||
}
|
||||
return a.exportResultsXLSX(results, savePath)
|
||||
return a.exportResultsXLSX(results, savePath, includeHdr)
|
||||
}
|
||||
|
||||
// exportHeaders 构建导出表头行(使用真实表头或回退默认)
|
||||
func (a *App) exportHeaders(numACols int) []string {
|
||||
a.dataMu.RLock()
|
||||
hdrA := make([]string, len(a.headersA))
|
||||
copy(hdrA, a.headersA)
|
||||
a.dataMu.RUnlock()
|
||||
|
||||
headers := make([]string, 0, numACols+1)
|
||||
if len(a.headersA) >= numACols {
|
||||
for _, h := range a.headersA[:numACols] {
|
||||
if len(hdrA) >= numACols {
|
||||
for _, h := range hdrA[:numACols] {
|
||||
n := h
|
||||
if n == "" {
|
||||
n = fmt.Sprintf("Col%d", len(headers)+1)
|
||||
@@ -1144,7 +1324,7 @@ func (a *App) exportHeaders(numACols int) []string {
|
||||
return headers
|
||||
}
|
||||
|
||||
func (a *App) exportResultsXLSX(results []MatchResult, savePath string) (string, error) {
|
||||
func (a *App) exportResultsXLSX(results []MatchResult, savePath string, includeHeader bool) (string, error) {
|
||||
f := excelize.NewFile()
|
||||
defer f.Close()
|
||||
sheetName := "匹配结果"
|
||||
@@ -1157,7 +1337,7 @@ func (a *App) exportResultsXLSX(results []MatchResult, savePath string) (string,
|
||||
extractCol := numACols
|
||||
|
||||
// 表头
|
||||
if a.lastConfig.IncludeHeader {
|
||||
if includeHeader {
|
||||
for i, h := range headers {
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("%s1", colLetter(i)), h)
|
||||
}
|
||||
@@ -1166,12 +1346,41 @@ func (a *App) exportResultsXLSX(results []MatchResult, savePath string) (string,
|
||||
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"4472C4"}},
|
||||
})
|
||||
f.SetCellStyle(sheetName, "A1", fmt.Sprintf("%s1", colLetter(extractCol)), headerStyle)
|
||||
// 数据行样式(带边框和行号字体)
|
||||
dataStyle, _ := f.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Size: 11},
|
||||
Border: []excelize.Border{
|
||||
{Type: "bottom", Color: "D9D9D9", Style: 1},
|
||||
},
|
||||
})
|
||||
firstDataRow := 2
|
||||
lastDataRow := len(results) + 1
|
||||
for ci := 0; ci <= numACols; ci++ {
|
||||
f.SetCellStyle(sheetName,
|
||||
fmt.Sprintf("%s%d", colLetter(ci), firstDataRow),
|
||||
fmt.Sprintf("%s%d", colLetter(ci), lastDataRow),
|
||||
dataStyle)
|
||||
}
|
||||
} else {
|
||||
dataStyle, _ := f.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Size: 11},
|
||||
Border: []excelize.Border{
|
||||
{Type: "bottom", Color: "D9D9D9", Style: 1},
|
||||
},
|
||||
})
|
||||
lastDataRow := len(results)
|
||||
for ci := 0; ci <= numACols; ci++ {
|
||||
f.SetCellStyle(sheetName,
|
||||
fmt.Sprintf("%s%d", colLetter(ci), 1),
|
||||
fmt.Sprintf("%s%d", colLetter(ci), lastDataRow),
|
||||
dataStyle)
|
||||
}
|
||||
}
|
||||
|
||||
// 数据行
|
||||
for i, r := range results {
|
||||
rowNum := i + 2
|
||||
if !a.lastConfig.IncludeHeader {
|
||||
if !includeHeader {
|
||||
rowNum = i + 1
|
||||
}
|
||||
for ci := 0; ci < numACols; ci++ {
|
||||
@@ -1191,7 +1400,7 @@ func (a *App) exportResultsXLSX(results []MatchResult, savePath string) (string,
|
||||
return savePath, nil
|
||||
}
|
||||
|
||||
func (a *App) exportResultsCSV(results []MatchResult, savePath string) (string, error) {
|
||||
func (a *App) exportResultsCSV(results []MatchResult, savePath string, includeHeader bool) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
// 使用 UTF-8 BOM 帮助 Excel 正确识别编码
|
||||
buf.Write([]byte{0xEF, 0xBB, 0xBF})
|
||||
@@ -1200,7 +1409,7 @@ func (a *App) exportResultsCSV(results []MatchResult, savePath string) (string,
|
||||
headers := a.exportHeaders(numACols)
|
||||
|
||||
// 表头行
|
||||
if a.lastConfig.IncludeHeader {
|
||||
if includeHeader {
|
||||
for i, h := range headers {
|
||||
if i > 0 {
|
||||
buf.WriteByte(',')
|
||||
|
||||
884
frontend/package-lock.json
generated
Normal file
884
frontend/package-lock.json
generated
Normal file
@@ -0,0 +1,884 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"vue": "^3.2.37"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.0.3",
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
||||
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.29.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
|
||||
"integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
|
||||
"integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz",
|
||||
"integrity": "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^3.0.0",
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz",
|
||||
"integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.3",
|
||||
"@vue/shared": "3.5.34",
|
||||
"entities": "^7.0.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz",
|
||||
"integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.34",
|
||||
"@vue/shared": "3.5.34"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz",
|
||||
"integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.3",
|
||||
"@vue/compiler-core": "3.5.34",
|
||||
"@vue/compiler-dom": "3.5.34",
|
||||
"@vue/compiler-ssr": "3.5.34",
|
||||
"@vue/shared": "3.5.34",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"postcss": "^8.5.14",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz",
|
||||
"integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.34",
|
||||
"@vue/shared": "3.5.34"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz",
|
||||
"integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.34"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz",
|
||||
"integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.34",
|
||||
"@vue/shared": "3.5.34"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz",
|
||||
"integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.34",
|
||||
"@vue/runtime-core": "3.5.34",
|
||||
"@vue/shared": "3.5.34",
|
||||
"csstype": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz",
|
||||
"integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.34",
|
||||
"@vue/shared": "3.5.34"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.34"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz",
|
||||
"integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
|
||||
"integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/android-arm": "0.15.18",
|
||||
"@esbuild/linux-loong64": "0.15.18",
|
||||
"esbuild-android-64": "0.15.18",
|
||||
"esbuild-android-arm64": "0.15.18",
|
||||
"esbuild-darwin-64": "0.15.18",
|
||||
"esbuild-darwin-arm64": "0.15.18",
|
||||
"esbuild-freebsd-64": "0.15.18",
|
||||
"esbuild-freebsd-arm64": "0.15.18",
|
||||
"esbuild-linux-32": "0.15.18",
|
||||
"esbuild-linux-64": "0.15.18",
|
||||
"esbuild-linux-arm": "0.15.18",
|
||||
"esbuild-linux-arm64": "0.15.18",
|
||||
"esbuild-linux-mips64le": "0.15.18",
|
||||
"esbuild-linux-ppc64le": "0.15.18",
|
||||
"esbuild-linux-riscv64": "0.15.18",
|
||||
"esbuild-linux-s390x": "0.15.18",
|
||||
"esbuild-netbsd-64": "0.15.18",
|
||||
"esbuild-openbsd-64": "0.15.18",
|
||||
"esbuild-sunos-64": "0.15.18",
|
||||
"esbuild-windows-32": "0.15.18",
|
||||
"esbuild-windows-64": "0.15.18",
|
||||
"esbuild-windows-arm64": "0.15.18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
|
||||
"integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
|
||||
"integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
|
||||
"integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
|
||||
"integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
|
||||
"integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
|
||||
"integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
|
||||
"integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-riscv64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
|
||||
"integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-s390x": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
|
||||
"integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
|
||||
"integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
|
||||
"integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
|
||||
"integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
|
||||
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.2",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
|
||||
"integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.12",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
|
||||
"integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"is-core-module": "^2.16.1",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.80.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
|
||||
"integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz",
|
||||
"integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.15.9",
|
||||
"postcss": "^8.4.18",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^2.79.1"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 14",
|
||||
"less": "*",
|
||||
"sass": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz",
|
||||
"integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.34",
|
||||
"@vue/compiler-sfc": "3.5.34",
|
||||
"@vue/runtime-dom": "3.5.34",
|
||||
"@vue/server-renderer": "3.5.34",
|
||||
"@vue/shared": "3.5.34"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
frontend/wailsjs/go/main/App.d.ts
vendored
6
frontend/wailsjs/go/main/App.d.ts
vendored
@@ -8,11 +8,9 @@ export function CleanString(arg1:string):Promise<string>;
|
||||
|
||||
export function ClearAICache():Promise<string>;
|
||||
|
||||
export function DeepseekEnhanceMatching(arg1:string,arg2:string):Promise<Array<main.MatchResult>>;
|
||||
|
||||
export function ExportResults(arg1:Array<main.MatchResult>):Promise<string>;
|
||||
|
||||
export function GetAICacheInfo():Promise<Record<string, any>>;
|
||||
export function GetAICacheInfo():Promise<main.AICacheInfo>;
|
||||
|
||||
export function GetDeepseekStatus():Promise<boolean>;
|
||||
|
||||
@@ -31,5 +29,3 @@ export function RunMatch(arg1:main.MatchConfig):Promise<Array<main.MatchResult>>
|
||||
export function RunMatchWithAI(arg1:main.MatchConfig):Promise<Array<main.MatchResult>>;
|
||||
|
||||
export function SetDeepseekAPIKey(arg1:string):Promise<string>;
|
||||
|
||||
export function StartMatching(arg1:string,arg2:string):Promise<Array<main.MatchResult>>;
|
||||
|
||||
@@ -14,10 +14,6 @@ export function ClearAICache() {
|
||||
return window['go']['main']['App']['ClearAICache']();
|
||||
}
|
||||
|
||||
export function DeepseekEnhanceMatching(arg1, arg2) {
|
||||
return window['go']['main']['App']['DeepseekEnhanceMatching'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ExportResults(arg1) {
|
||||
return window['go']['main']['App']['ExportResults'](arg1);
|
||||
}
|
||||
@@ -61,7 +57,3 @@ export function RunMatchWithAI(arg1) {
|
||||
export function SetDeepseekAPIKey(arg1) {
|
||||
return window['go']['main']['App']['SetDeepseekAPIKey'](arg1);
|
||||
}
|
||||
|
||||
export function StartMatching(arg1, arg2) {
|
||||
return window['go']['main']['App']['StartMatching'](arg1, arg2);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
export namespace main {
|
||||
|
||||
export class AICacheInfo {
|
||||
count: number;
|
||||
filePath: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AICacheInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.count = source["count"];
|
||||
this.filePath = source["filePath"];
|
||||
}
|
||||
}
|
||||
export class MatchConfig {
|
||||
fileAPath: string;
|
||||
fileBPath: string;
|
||||
@@ -46,9 +60,6 @@ export namespace main {
|
||||
rowAData: string[];
|
||||
rowBKey: string;
|
||||
extractValue: string;
|
||||
monthlyCellName: string;
|
||||
dailyCellId: string;
|
||||
interruptReason: string;
|
||||
timeDiff: string;
|
||||
similarityScore: number;
|
||||
aiMatched: boolean;
|
||||
@@ -62,9 +73,6 @@ export namespace main {
|
||||
this.rowAData = source["rowAData"];
|
||||
this.rowBKey = source["rowBKey"];
|
||||
this.extractValue = source["extractValue"];
|
||||
this.monthlyCellName = source["monthlyCellName"];
|
||||
this.dailyCellId = source["dailyCellId"];
|
||||
this.interruptReason = source["interruptReason"];
|
||||
this.timeDiff = source["timeDiff"];
|
||||
this.similarityScore = source["similarityScore"];
|
||||
this.aiMatched = source["aiMatched"];
|
||||
|
||||
Reference in New Issue
Block a user