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