File size: 5,715 Bytes
5fc68b0
136f9cf
5fc68b0
 
 
 
 
136f9cf
5fc68b0
 
 
 
 
 
136f9cf
5fc68b0
 
 
 
 
 
 
 
 
136f9cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5fc68b0
 
 
136f9cf
 
 
 
 
 
 
 
 
 
 
 
5fc68b0
136f9cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5fc68b0
 
 
 
 
 
 
 
 
 
 
 
136f9cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5fc68b0
 
 
 
 
 
 
 
 
 
136f9cf
5fc68b0
 
136f9cf
 
 
 
 
5fc68b0
 
136f9cf
 
 
 
5fc68b0
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import Dexie, { Table } from "dexie";
import { IConfig, DEFAULT_CONFIG, loadAllModels } from "./types";

export class ConfigManager extends Dexie {
  configs!: Table<IConfig>;
  private static instance: ConfigManager;
  private currentConfig: IConfig | null = null;
  private initPromise: Promise<IConfig> | null = null;

  private constructor() {
    super("configs");
    this.version(1).stores({
      configs: "id, createdAt, updatedAt",
    });
    this.initPromise = this.initialize();
  }

  public static getInstance(): ConfigManager {
    if (!ConfigManager.instance) {
      ConfigManager.instance = new ConfigManager();
    }
    return ConfigManager.instance;
  }

  async initialize(): Promise<IConfig> {
    // Return existing config or in-progress initialization
    if (this.currentConfig) return this.currentConfig;
    if (this.initPromise) return this.initPromise;
    
    try {
      const configs = await this.configs.toArray();
      
      if (configs.length === 0) {
        // Create default config if none exists
        const defaultConfig: IConfig = {
          ...DEFAULT_CONFIG,
          id: crypto.randomUUID(),
          createdAt: Date.now(),
          updatedAt: Date.now(),
          ollama_available: false
        };
        
        // Check Ollama availability if URL is provided
        if (defaultConfig.ollama_base_url.trim() !== '') {
          defaultConfig.ollama_available = await this.checkOllamaAvailability(defaultConfig.ollama_base_url);
        }
        
        await this.configs.add(defaultConfig);
        this.currentConfig = defaultConfig;
      } else {
        // Use the most recently updated config
        this.currentConfig = configs.sort((a, b) => b.updatedAt - a.updatedAt)[0];
        
        // Check Ollama availability if URL is provided
        if (this.currentConfig.ollama_base_url.trim() !== '') {
          const ollamaAvailable = await this.checkOllamaAvailability(this.currentConfig.ollama_base_url);
          
          // Update availability if changed
          if (ollamaAvailable !== this.currentConfig.ollama_available) {
            await this.updateConfig({ ollama_available: ollamaAvailable });
          }
        }
      }
      
      // Load models after config is ready
      await this.loadModels();
      return this.currentConfig;
    } catch (error) {
      console.error("Error initializing config:", error);
      throw error;
    } finally {
      this.initPromise = null;
    }
  }

  private async checkOllamaAvailability(baseUrl: string): Promise<boolean> {
    if (!baseUrl || baseUrl.trim() === '') return false;
    
    try {
      const response = await fetch(`${baseUrl}/api/tags`);
      if (!response.ok) return false;
      
      const data = await response.json();
      return Array.isArray(data.models) && data.models.length > 0;
    } catch (error) {
      console.error("Ollama server not available:", error);
      return false;
    }
  }

  private async loadModels(): Promise<void> {
    if (!this.currentConfig) return;
    
    try {
      const { ollamaAvailable } = await loadAllModels(
        this.currentConfig.ollama_base_url,
        this.currentConfig.openai_model,
        this.currentConfig.hf_custom_models
      );
      
      // Update availability if changed
      if (ollamaAvailable !== this.currentConfig.ollama_available) {
        await this.updateConfig({ ollama_available: ollamaAvailable });
      }
    } catch (error) {
      console.error("Error loading models:", error);
    }
  }

  async getConfig(): Promise<IConfig> {
    return this.currentConfig || this.initialize();
  }

  async updateConfig(updates: Partial<Omit<IConfig, 'id' | 'createdAt' | 'updatedAt'>>): Promise<IConfig> {
    const current = await this.getConfig();
    const updatedConfig: IConfig = {
      ...current,
      ...updates,
      updatedAt: Date.now(),
    };
    
    await this.configs.put(updatedConfig);
    this.currentConfig = updatedConfig;
    
    // Handle Ollama URL updates
    if (updates.ollama_base_url !== undefined && 
        updates.ollama_base_url !== current.ollama_base_url) {
      
      // Check availability only if URL is not empty
      const ollamaAvailable = updates.ollama_base_url.trim() === '' 
        ? false 
        : await this.checkOllamaAvailability(updates.ollama_base_url);
      
      if (ollamaAvailable !== this.currentConfig.ollama_available) {
        return this.updateConfig({ ollama_available: ollamaAvailable });
      }
      
      // Reload models if URL changed
      await this.loadModels();
    }
    
    // Reload models if OpenAI custom models changed
    if (updates.openai_model !== undefined &&
        updates.openai_model !== current.openai_model) {
      await this.loadModels();
    }
    
    // Reload models if Hugging Face custom models changed
    if (updates.hf_custom_models !== undefined &&
        updates.hf_custom_models !== current.hf_custom_models) {
      await this.loadModels();
    }
    
    return updatedConfig;
  }

  async resetToDefaults(): Promise<IConfig> {
    const current = await this.getConfig();
    const resetConfig: IConfig = {
      ...DEFAULT_CONFIG,
      id: current.id,
      createdAt: current.createdAt,
      updatedAt: Date.now(),
      ollama_available: false
    };
    
    // Check Ollama availability if URL is provided
    if (resetConfig.ollama_base_url.trim() !== '') {
      resetConfig.ollama_available = await this.checkOllamaAvailability(resetConfig.ollama_base_url);
    }
    
    await this.configs.put(resetConfig);
    this.currentConfig = resetConfig;
    
    // Reload models after reset
    await this.loadModels();
    
    return resetConfig;
  }
}