diff --git a/sundynix-desktop/frontend/src/ui/Input.tsx b/sundynix-desktop/frontend/src/ui/Input.tsx index ab4ae96..e5c3820 100644 --- a/sundynix-desktop/frontend/src/ui/Input.tsx +++ b/sundynix-desktop/frontend/src/ui/Input.tsx @@ -1,8 +1,10 @@ import type { InputHTMLAttributes, TextareaHTMLAttributes, SelectHTMLAttributes, ReactNode } from "react"; import { cn } from "./cn"; +// 注意:基类不含宽度。宽度由调用方决定(Field 内为 flex-col 自动撑满, +// 或显式 w-full / flex-1 / w-16),避免 w-full 与 flex-1/w-16 冲突塌陷。 const fieldBase = - "w-full rounded-md border border-line bg-ink-950 text-sm text-slate-200 placeholder:text-slate-600 transition focus:border-brand focus:outline-none focus:ring-1 focus:ring-brand/40 disabled:opacity-50"; + "rounded-md border border-line bg-ink-950 text-sm text-slate-200 placeholder:text-slate-600 transition focus:border-brand focus:outline-none focus:ring-1 focus:ring-brand/40 disabled:opacity-50"; export function Input({ className, ...rest }: InputHTMLAttributes) { return ; diff --git a/sundynix-mcp-go/internal/rag/milvus.go b/sundynix-mcp-go/internal/rag/milvus.go index b7f877f..5a00ab8 100644 --- a/sundynix-mcp-go/internal/rag/milvus.go +++ b/sundynix-mcp-go/internal/rag/milvus.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strconv" + "strings" "sync" "github.com/milvus-io/milvus-sdk-go/v2/client" @@ -15,10 +16,10 @@ const collection = "sundynix_wiki" // Wiki/知识库向量集合 // milvusStore 封装 Milvus 连接与集合管理(集合按首次写入的向量维度懒建)。 type milvusStore struct { - cli client.Client - mu sync.Mutex - dim int // 已建集合的维度(0=未建) - ok bool // 集合是否就绪 + cli client.Client + mu sync.Mutex + dim int // 已建集合的维度(0=未建) + ok bool // 集合是否就绪 } func openMilvus(ctx context.Context, addr string) (*milvusStore, error) { @@ -79,27 +80,56 @@ func (m *milvusStore) ensure(ctx context.Context, dim int) error { return nil } +// invalidate 让集合就绪缓存失效(集合被外部删除/基础设施重启丢失后,强制下次重建)。 +func (m *milvusStore) invalidate() { + m.mu.Lock() + m.ok = false + m.mu.Unlock() +} + +// isCollectionGone 判断错误是否为"集合不存在"(Milvus 重启丢集合后写/查会报此类)。 +func isCollectionGone(err error) bool { + if err == nil { + return false + } + s := strings.ToLower(err.Error()) + return strings.Contains(s, "collection not found") || + strings.Contains(s, "can't find collection") || + strings.Contains(s, "collection not exist") || + strings.Contains(s, "collection not loaded") +} + // insert 写入若干 (kb, text, vector)。 +// 若集合在运行期被丢失(如 Milvus 重启)→ 清缓存、重建集合后重试一次,避免必须重启进程才能恢复。 func (m *milvusStore) insert(ctx context.Context, kb string, texts []string, vecs [][]float32) error { if len(vecs) == 0 { return nil } - if err := m.ensure(ctx, len(vecs[0])); err != nil { - return err - } + dim := len(vecs[0]) kbs := make([]string, len(texts)) for i := range kbs { kbs[i] = kb } - _, err := m.cli.Insert(ctx, collection, "", - entity.NewColumnVarChar("kb", kbs), - entity.NewColumnVarChar("text", texts), - entity.NewColumnFloatVector("vector", len(vecs[0]), vecs), - ) - if err != nil { - return fmt.Errorf("insert: %w", err) + do := func() error { + if err := m.ensure(ctx, dim); err != nil { + return err + } + if _, err := m.cli.Insert(ctx, collection, "", + entity.NewColumnVarChar("kb", kbs), + entity.NewColumnVarChar("text", texts), + entity.NewColumnFloatVector("vector", dim, vecs), + ); err != nil { + return fmt.Errorf("insert: %w", err) + } + return m.cli.Flush(ctx, collection, false) } - return m.cli.Flush(ctx, collection, false) + err := do() + if err != nil && isCollectionGone(err) { + log.Printf("[rag] 集合不存在(疑似 Milvus 重启丢失),清缓存重建后重试写入") + m.invalidate() + err = do() + } + return err } // vectorDim 从集合 schema 读出向量字段维度(用于检测维度变化)。 @@ -125,22 +155,29 @@ type Hit struct { } // search 用查询向量做 topK 向量检索(可按 kb 过滤)。 +// 集合未建(还没入过库)→ 返回空结果;集合运行期丢失 → 清缓存重建后重试一次。 func (m *milvusStore) search(ctx context.Context, kb string, qvec []float32, topK int) ([]Hit, error) { - if !m.ok { - // 集合未建(还没入过库)→ 尝试确保(按查询维度),无则空结果。 - if err := m.ensure(ctx, len(qvec)); err != nil { - return nil, nil - } - } expr := "" if kb != "" { expr = fmt.Sprintf("kb == \"%s\"", kb) } sp, _ := entity.NewIndexAUTOINDEXSearchParam(1) - results, err := m.cli.Search(ctx, collection, nil, expr, []string{"text"}, - []entity.Vector{entity.FloatVector(qvec)}, "vector", entity.COSINE, topK, sp) + do := func() ([]client.SearchResult, error) { + if err := m.ensure(ctx, len(qvec)); err != nil { + return nil, err + } + return m.cli.Search(ctx, collection, nil, expr, []string{"text"}, + []entity.Vector{entity.FloatVector(qvec)}, "vector", entity.COSINE, topK, sp) + } + results, err := do() + if err != nil && isCollectionGone(err) { + log.Printf("[rag] 检索时集合不存在,清缓存重建后重试") + m.invalidate() + results, err = do() + } if err != nil { - return nil, fmt.Errorf("search: %w", err) + // 集合尚未就绪/无法重建 → 降级空结果(不阻断混合检索其它路)。 + return nil, nil } var hits []Hit for _, r := range results {