diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..366d35b --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +OPENAI_API_KEY=sk-your-api-key-here +OPENAI_BASE_URL=https://api.openai.com/v1 +OPENAI_MODEL=gpt-4o +OPENAI_EMBED_MODEL=text-embedding-3-small +DATA_DIR=./data +PORT=8080 diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..8167c51 --- /dev/null +++ b/config/config.go @@ -0,0 +1,30 @@ +package config + +import "os" + +type Config struct { + OpenAIAPIKey string + OpenAIBaseURL string + OpenAIModel string + OpenAIEmbedModel string + DataDir string + Port string +} + +func Load() *Config { + return &Config{ + OpenAIAPIKey: getEnv("OPENAI_API_KEY", ""), + OpenAIBaseURL: getEnv("OPENAI_BASE_URL", "https://api.openai.com/v1"), + OpenAIModel: getEnv("OPENAI_MODEL", "gpt-4o"), + OpenAIEmbedModel: getEnv("OPENAI_EMBED_MODEL", "text-embedding-3-small"), + DataDir: getEnv("DATA_DIR", "./data"), + Port: getEnv("PORT", "8080"), + } +} + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..28b9fa5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module eino-test + +go 1.23.6 diff --git a/internal/store/memory.go b/internal/store/memory.go new file mode 100644 index 0000000..6bfcc86 --- /dev/null +++ b/internal/store/memory.go @@ -0,0 +1,83 @@ +package store + +import ( + "context" + "fmt" + "strings" + "sync" +) + +type MemoryNoteStore struct { + mu sync.RWMutex + notes map[string]*Note +} + +func NewMemoryNoteStore() *MemoryNoteStore { + return &MemoryNoteStore{ + notes: make(map[string]*Note), + } +} + +func (s *MemoryNoteStore) GetByID(_ context.Context, id string) (*Note, error) { + s.mu.RLock() + defer s.mu.RUnlock() + n, ok := s.notes[id] + if !ok { + return nil, fmt.Errorf("note not found: %s", id) + } + return n, nil +} + +func (s *MemoryNoteStore) List(_ context.Context) ([]*Note, error) { + s.mu.RLock() + defer s.mu.RUnlock() + result := make([]*Note, 0, len(s.notes)) + for _, n := range s.notes { + result = append(result, n) + } + return result, nil +} + +func (s *MemoryNoteStore) Create(_ context.Context, note *Note) error { + s.mu.Lock() + defer s.mu.Unlock() + if _, exists := s.notes[note.ID]; exists { + return fmt.Errorf("note already exists: %s", note.ID) + } + s.notes[note.ID] = note + return nil +} + +func (s *MemoryNoteStore) Update(_ context.Context, note *Note) error { + s.mu.Lock() + defer s.mu.Unlock() + if _, exists := s.notes[note.ID]; !exists { + return fmt.Errorf("note not found: %s", note.ID) + } + s.notes[note.ID] = note + return nil +} + +func (s *MemoryNoteStore) Delete(_ context.Context, id string) error { + s.mu.Lock() + defer s.mu.Unlock() + if _, exists := s.notes[id]; !exists { + return fmt.Errorf("note not found: %s", id) + } + delete(s.notes, id) + return nil +} + +func (s *MemoryNoteStore) SearchByKeyword(_ context.Context, keyword string) ([]*Note, error) { + s.mu.RLock() + defer s.mu.RUnlock() + keyword = strings.ToLower(keyword) + var result []*Note + for _, n := range s.notes { + if strings.Contains(strings.ToLower(n.Title), keyword) || + strings.Contains(strings.ToLower(n.Content), keyword) { + result = append(result, n) + } + } + return result, nil +} diff --git a/internal/store/models.go b/internal/store/models.go new file mode 100644 index 0000000..c8607ce --- /dev/null +++ b/internal/store/models.go @@ -0,0 +1,25 @@ +package store + +import "time" + +type Note struct { + ID string `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Tags []string `json:"tags"` + FilePath string `json:"file_path"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type Chunk struct { + NoteID string + Index int + Content string + Embedding []float64 +} + +type SearchResult struct { + Note *Note `json:"note"` + Score float64 `json:"score"` +} diff --git a/internal/store/store.go b/internal/store/store.go new file mode 100644 index 0000000..64a5efe --- /dev/null +++ b/internal/store/store.go @@ -0,0 +1,12 @@ +package store + +import "context" + +type NoteStore interface { + GetByID(ctx context.Context, id string) (*Note, error) + List(ctx context.Context) ([]*Note, error) + Create(ctx context.Context, note *Note) error + Update(ctx context.Context, note *Note) error + Delete(ctx context.Context, id string) error + SearchByKeyword(ctx context.Context, keyword string) ([]*Note, error) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..ea57497 --- /dev/null +++ b/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "log" + + "eino-test/config" + "eino-test/internal/store" +) + +func main() { + cfg := config.Load() + noteStore := store.NewMemoryNoteStore() + _ = noteStore + + log.Printf("Server starting on port %s (data dir: %s)", cfg.Port, cfg.DataDir) + fmt.Println("Knowledge Assistant ready") +}