1<?php 2 3namespace dokuwiki\plugin\aichat; 4 5use dokuwiki\plugin\aichat\Model\ChatInterface; 6use dokuwiki\plugin\aichat\Model\EmbeddingInterface; 7 8class ModelFactory 9{ 10 /** @var array The plugin configuration */ 11 protected array $config; 12 13 public $chatModel; 14 public $rephraseModel; 15 public $embeddingModel; 16 17 protected $debug = false; 18 19 /** 20 * @param array $config The plugin configuration 21 */ 22 public function __construct(array $config) 23 { 24 $this->config = $config; 25 } 26 27 /** 28 * Update the configuration and reset the cached models 29 * 30 * @param array $config The new (partial) configuration 31 */ 32 public function updateConfig(array $config) 33 { 34 $this->config = array_merge($this->config, $config); 35 $this->chatModel = null; 36 $this->rephraseModel = null; 37 $this->embeddingModel = null; 38 } 39 40 /** 41 * Set the debug flag for all models 42 * 43 * @param bool $debug 44 */ 45 public function setDebug(bool $debug = true) 46 { 47 $this->debug = $debug; 48 $this->getChatModel()->setDebug($debug); 49 $this->getRephraseModel()->setDebug($debug); 50 $this->getEmbeddingModel()->setDebug($debug); 51 } 52 53 /** 54 * Access a cached Chat Model 55 * 56 * @return ChatInterface 57 * @throws \Exception 58 */ 59 public function getChatModel() 60 { 61 if ($this->chatModel instanceof ChatInterface) { 62 return $this->chatModel; 63 } 64 $this->chatModel = $this->loadModel('chat', $this->config['chatmodel']); 65 $this->chatModel->setDebug($this->debug); 66 return $this->chatModel; 67 } 68 69 /** 70 * Access a cached Rephrase Model 71 * 72 * @return ChatInterface 73 * @throws \Exception 74 */ 75 public function getRephraseModel() 76 { 77 if ($this->rephraseModel instanceof ChatInterface) { 78 return $this->rephraseModel; 79 } 80 $this->rephraseModel = $this->loadModel('chat', $this->config['chatmodel']); 81 $this->rephraseModel->setDebug($this->debug); 82 return $this->rephraseModel; 83 } 84 85 /** 86 * Access a cached Embedding Model 87 * 88 * @return EmbeddingInterface 89 */ 90 public function getEmbeddingModel() 91 { 92 if ($this->embeddingModel instanceof EmbeddingInterface) { 93 return $this->embeddingModel; 94 } 95 $this->embeddingModel = $this->loadModel('embedding', $this->config['embedmodel']); 96 $this->embeddingModel->setDebug($this->debug); 97 return $this->embeddingModel; 98 } 99 100 /** 101 * Get all known models 102 * 103 * A (new) instance is returned for each model that is available through the current configuration. 104 * 105 * @param bool $availableOnly Only return models that are available 106 * @param string $typeOnly Only return models of this type ('chat' or 'embedding') 107 * @return array 108 */ 109 public function getModels($availableOnly = false, $typeOnly = '') 110 { 111 $result = [ 112 'chat' => [], 113 'embedding' => [], 114 ]; 115 116 $jsons = glob(__DIR__ . '/Model/*/models.json'); 117 foreach ($jsons as $json) { 118 $models = json_decode(file_get_contents($json), true); 119 foreach ($models as $type => $model) { 120 $namespace = basename(dirname($json)); 121 foreach ($model as $name => $info) { 122 try { 123 $info['instance'] = $this->loadModel($type, "$namespace $name"); 124 $info['instance']->setDebug($this->debug); 125 } catch (\Exception $e) { 126 if ($availableOnly) continue; 127 $info['instance'] = false; 128 } 129 130 $result[$type]["$namespace $name"] = $info; 131 } 132 } 133 } 134 135 return $typeOnly ? $result[$typeOnly] : $result; 136 } 137 138 139 /** 140 * Initialize a model by config name 141 * 142 * @param string $type 'chat' or 'embedding' 143 * @param string $name The full model name including provider 144 * @return ChatInterface|EmbeddingInterface 145 * @throws \Exception 146 */ 147 public function loadModel(string $type, string $name) 148 { 149 $type = ucfirst(strtolower($type)); 150 $prefix = '\\dokuwiki\\plugin\\aichat\\Model\\'; 151 $cname = $type . 'Model'; 152 $interface = $prefix . $type . 'Interface'; 153 154 155 [$namespace, $model] = sexplode(' ', $name, 2, ''); 156 $class = $prefix . $namespace . '\\' . $cname; 157 158 if (!class_exists($class)) { 159 throw new \Exception("No $cname found for $namespace"); 160 } 161 162 try { 163 $instance = new $class($model, $this->config); 164 } catch (\Exception $e) { 165 throw new \Exception("Failed to initialize $cname for $namespace: " . $e->getMessage(), 0, $e); 166 } 167 168 if (!($instance instanceof $interface)) { 169 throw new \Exception("$cname for $namespace does not implement $interface"); 170 } 171 172 return $instance; 173 } 174} 175