homeassistant 2025.7.0b7__py3-none-any.whl → 2025.7.0b9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. homeassistant/components/ai_task/translations/nl.json +3 -0
  2. homeassistant/components/alexa_devices/config_flow.py +3 -1
  3. homeassistant/components/alexa_devices/manifest.json +1 -1
  4. homeassistant/components/alexa_devices/strings.json +1 -0
  5. homeassistant/components/alexa_devices/translations/de.json +2 -1
  6. homeassistant/components/alexa_devices/translations/en.json +2 -1
  7. homeassistant/components/alexa_devices/translations/es.json +2 -1
  8. homeassistant/components/alexa_devices/translations/et.json +2 -1
  9. homeassistant/components/alexa_devices/translations/ga.json +11 -0
  10. homeassistant/components/alexa_devices/translations/lt.json +29 -2
  11. homeassistant/components/alexa_devices/translations/mk.json +8 -0
  12. homeassistant/components/alexa_devices/translations/sk.json +2 -1
  13. homeassistant/components/alexa_devices/translations/zh-Hant.json +2 -1
  14. homeassistant/components/androidtv_remote/translations/ko.json +1 -1
  15. homeassistant/components/aosmith/translations/ko.json +1 -1
  16. homeassistant/components/assist_satellite/translations/nl.json +7 -0
  17. homeassistant/components/aussie_broadband/translations/ko.json +1 -1
  18. homeassistant/components/bluetooth/manifest.json +1 -1
  19. homeassistant/components/bosch_shc/translations/ko.json +1 -1
  20. homeassistant/components/brunt/translations/ko.json +1 -1
  21. homeassistant/components/caldav/translations/ko.json +1 -1
  22. homeassistant/components/devolo_home_control/translations/lt.json +10 -0
  23. homeassistant/components/ecovacs/manifest.json +1 -1
  24. homeassistant/components/electric_kiwi/translations/ko.json +1 -1
  25. homeassistant/components/emoncms/translations/ga.json +1 -0
  26. homeassistant/components/enphase_envoy/translations/et.json +1 -0
  27. homeassistant/components/enphase_envoy/translations/lt.json +1 -0
  28. homeassistant/components/enphase_envoy/translations/nl.json +2 -1
  29. homeassistant/components/ezviz/translations/lt.json +10 -0
  30. homeassistant/components/fibaro/translations/ko.json +1 -1
  31. homeassistant/components/fitbit/translations/ko.json +1 -1
  32. homeassistant/components/frontend/manifest.json +1 -1
  33. homeassistant/components/geocaching/translations/ko.json +1 -1
  34. homeassistant/components/google/translations/ko.json +1 -1
  35. homeassistant/components/google_generative_ai_conversation/config_flow.py +5 -4
  36. homeassistant/components/google_generative_ai_conversation/translations/lt.json +28 -0
  37. homeassistant/components/google_photos/translations/ko.json +1 -1
  38. homeassistant/components/google_sheets/translations/ko.json +1 -1
  39. homeassistant/components/google_tasks/translations/ko.json +1 -1
  40. homeassistant/components/google_travel_time/translations/it.json +7 -0
  41. homeassistant/components/hassio/ingress.py +6 -2
  42. homeassistant/components/home_connect/translations/it.json +3 -0
  43. homeassistant/components/homeassistant/translations/it.json +24 -0
  44. homeassistant/components/homeassistant_hardware/translations/et.json +1 -0
  45. homeassistant/components/homeassistant_hardware/translations/ga.json +4 -0
  46. homeassistant/components/homeassistant_hardware/translations/lt.json +2 -0
  47. homeassistant/components/homeassistant_hardware/translations/mk.json +3 -0
  48. homeassistant/components/homeassistant_sky_connect/translations/et.json +2 -0
  49. homeassistant/components/homeassistant_sky_connect/translations/ga.json +8 -0
  50. homeassistant/components/homeassistant_sky_connect/translations/lt.json +4 -0
  51. homeassistant/components/homeassistant_yellow/translations/et.json +1 -0
  52. homeassistant/components/homeassistant_yellow/translations/ga.json +4 -0
  53. homeassistant/components/homeassistant_yellow/translations/lt.json +3 -0
  54. homeassistant/components/huawei_lte/translations/ko.json +1 -1
  55. homeassistant/components/icloud/translations/ko.json +1 -1
  56. homeassistant/components/immich/translations/nl.json +5 -0
  57. homeassistant/components/jellyfin/translations/ko.json +1 -1
  58. homeassistant/components/jvc_projector/translations/ko.json +1 -1
  59. homeassistant/components/laundrify/translations/ko.json +1 -1
  60. homeassistant/components/lcn/translations/cs.json +0 -3
  61. homeassistant/components/lcn/translations/de.json +1 -1
  62. homeassistant/components/lcn/translations/el.json +0 -3
  63. homeassistant/components/lcn/translations/en-GB.json +0 -3
  64. homeassistant/components/lcn/translations/en.json +1 -1
  65. homeassistant/components/lcn/translations/es.json +1 -1
  66. homeassistant/components/lcn/translations/et.json +0 -3
  67. homeassistant/components/lcn/translations/hu.json +0 -3
  68. homeassistant/components/lcn/translations/ja.json +0 -3
  69. homeassistant/components/lcn/translations/lt.json +0 -3
  70. homeassistant/components/lcn/translations/pt-BR.json +0 -3
  71. homeassistant/components/lcn/translations/pt.json +0 -3
  72. homeassistant/components/lcn/translations/ru.json +0 -3
  73. homeassistant/components/lcn/translations/sk.json +0 -3
  74. homeassistant/components/lcn/translations/sv.json +0 -3
  75. homeassistant/components/lcn/translations/tr.json +0 -3
  76. homeassistant/components/lcn/translations/zh-Hans.json +0 -3
  77. homeassistant/components/lcn/translations/zh-Hant.json +0 -3
  78. homeassistant/components/ld2410_ble/manifest.json +1 -1
  79. homeassistant/components/led_ble/manifest.json +1 -1
  80. homeassistant/components/lg_thinq/translations/et.json +3 -1
  81. homeassistant/components/lg_thinq/translations/lt.json +3 -1
  82. homeassistant/components/lidarr/translations/ko.json +1 -1
  83. homeassistant/components/litterrobot/translations/en-GB.json +1 -0
  84. homeassistant/components/litterrobot/translations/et.json +1 -0
  85. homeassistant/components/litterrobot/translations/ko.json +1 -1
  86. homeassistant/components/litterrobot/translations/lt.json +1 -0
  87. homeassistant/components/litterrobot/translations/sk.json +1 -0
  88. homeassistant/components/litterrobot/translations/zh-Hans.json +1 -0
  89. homeassistant/components/lyric/translations/it.json +3 -0
  90. homeassistant/components/lyric/translations/ko.json +1 -1
  91. homeassistant/components/melcloud/translations/ko.json +1 -1
  92. homeassistant/components/miele/translations/it.json +3 -0
  93. homeassistant/components/miele/translations/sk.json +1 -0
  94. homeassistant/components/mikrotik/translations/ko.json +1 -1
  95. homeassistant/components/mqtt/translations/bg.json +10 -0
  96. homeassistant/components/mqtt/translations/ga.json +10 -0
  97. homeassistant/components/mqtt/translations/it.json +7 -1
  98. homeassistant/components/mqtt/translations/lt.json +37 -0
  99. homeassistant/components/mqtt/translations/mk.json +9 -0
  100. homeassistant/components/nest/translations/ko.json +1 -1
  101. homeassistant/components/netatmo/translations/ko.json +1 -1
  102. homeassistant/components/nordpool/coordinator.py +3 -0
  103. homeassistant/components/notion/translations/ko.json +1 -1
  104. homeassistant/components/nuki/translations/ko.json +1 -1
  105. homeassistant/components/ollama/__init__.py +30 -4
  106. homeassistant/components/ollama/config_flow.py +164 -135
  107. homeassistant/components/ollama/conversation.py +8 -243
  108. homeassistant/components/ollama/entity.py +261 -0
  109. homeassistant/components/ollama/strings.json +12 -10
  110. homeassistant/components/ollama/translations/bg.json +0 -1
  111. homeassistant/components/ollama/translations/ca.json +0 -10
  112. homeassistant/components/ollama/translations/cs.json +1 -10
  113. homeassistant/components/ollama/translations/de.json +1 -10
  114. homeassistant/components/ollama/translations/el.json +1 -10
  115. homeassistant/components/ollama/translations/en-GB.json +0 -10
  116. homeassistant/components/ollama/translations/en.json +12 -10
  117. homeassistant/components/ollama/translations/es.json +1 -10
  118. homeassistant/components/ollama/translations/et.json +1 -10
  119. homeassistant/components/ollama/translations/fi.json +0 -7
  120. homeassistant/components/ollama/translations/fr.json +1 -10
  121. homeassistant/components/ollama/translations/ga.json +0 -3
  122. homeassistant/components/ollama/translations/hu.json +1 -10
  123. homeassistant/components/ollama/translations/it.json +0 -8
  124. homeassistant/components/ollama/translations/ja.json +1 -10
  125. homeassistant/components/ollama/translations/lt.json +1 -10
  126. homeassistant/components/ollama/translations/pt-BR.json +0 -3
  127. homeassistant/components/ollama/translations/pt.json +1 -10
  128. homeassistant/components/ollama/translations/ru.json +1 -10
  129. homeassistant/components/ollama/translations/sk.json +1 -10
  130. homeassistant/components/ollama/translations/sv.json +1 -10
  131. homeassistant/components/ollama/translations/tr.json +0 -10
  132. homeassistant/components/ollama/translations/zh-Hans.json +1 -10
  133. homeassistant/components/ollama/translations/zh-Hant.json +1 -10
  134. homeassistant/components/opentherm_gw/translations/bg.json +3 -0
  135. homeassistant/components/opentherm_gw/translations/et.json +3 -0
  136. homeassistant/components/opentherm_gw/translations/ga.json +3 -0
  137. homeassistant/components/opentherm_gw/translations/lt.json +3 -0
  138. homeassistant/components/opentherm_gw/translations/mk.json +7 -0
  139. homeassistant/components/openuv/translations/ko.json +1 -1
  140. homeassistant/components/opower/translations/ko.json +1 -1
  141. homeassistant/components/overkiz/translations/et.json +1 -0
  142. homeassistant/components/overkiz/translations/lt.json +1 -0
  143. homeassistant/components/overkiz/translations/zh-Hans.json +1 -0
  144. homeassistant/components/pegel_online/translations/lt.json +2 -1
  145. homeassistant/components/playstation_network/translations/cs.json +0 -3
  146. homeassistant/components/playstation_network/translations/el.json +0 -3
  147. homeassistant/components/playstation_network/translations/en.json +1 -1
  148. homeassistant/components/playstation_network/translations/et.json +1 -1
  149. homeassistant/components/playstation_network/translations/ga.json +0 -3
  150. homeassistant/components/playstation_network/translations/hu.json +0 -3
  151. homeassistant/components/playstation_network/translations/it.json +0 -3
  152. homeassistant/components/playstation_network/translations/lt.json +29 -0
  153. homeassistant/components/playstation_network/translations/mk.json +0 -3
  154. homeassistant/components/playstation_network/translations/nl.json +0 -3
  155. homeassistant/components/playstation_network/translations/pl.json +0 -3
  156. homeassistant/components/playstation_network/translations/pt.json +0 -3
  157. homeassistant/components/playstation_network/translations/sk.json +1 -1
  158. homeassistant/components/playstation_network/translations/sv.json +0 -3
  159. homeassistant/components/playstation_network/translations/zh-Hans.json +0 -3
  160. homeassistant/components/playstation_network/translations/zh-Hant.json +1 -1
  161. homeassistant/components/private_ble_device/manifest.json +1 -1
  162. homeassistant/components/proximity/translations/lt.json +25 -1
  163. homeassistant/components/proximity/translations/nl.json +3 -0
  164. homeassistant/components/pushover/translations/ko.json +1 -1
  165. homeassistant/components/radarr/translations/ko.json +1 -1
  166. homeassistant/components/renault/translations/ko.json +1 -1
  167. homeassistant/components/reolink/translations/it.json +3 -0
  168. homeassistant/components/reolink/translations/pl.json +2 -1
  169. homeassistant/components/ridwell/translations/ko.json +1 -1
  170. homeassistant/components/ring/translations/ko.json +1 -1
  171. homeassistant/components/roborock/translations/ko.json +1 -1
  172. homeassistant/components/russound_rio/translations/nl.json +7 -0
  173. homeassistant/components/schlage/translations/ko.json +1 -1
  174. homeassistant/components/scrape/translations/it.json +1 -0
  175. homeassistant/components/sense/translations/ko.json +1 -1
  176. homeassistant/components/sensibo/translations/pt-BR.json +4 -1
  177. homeassistant/components/sensibo/translations/sv.json +3 -3
  178. homeassistant/components/sensor/translations/it.json +1 -0
  179. homeassistant/components/shelly/__init__.py +8 -1
  180. homeassistant/components/shelly/const.py +3 -0
  181. homeassistant/components/shelly/repairs.py +87 -4
  182. homeassistant/components/shelly/strings.json +14 -0
  183. homeassistant/components/shelly/translations/bg.json +7 -0
  184. homeassistant/components/shelly/translations/de.json +14 -0
  185. homeassistant/components/shelly/translations/en.json +14 -0
  186. homeassistant/components/shelly/translations/es.json +14 -0
  187. homeassistant/components/shelly/translations/et.json +11 -0
  188. homeassistant/components/shelly/translations/ko.json +1 -1
  189. homeassistant/components/skybell/translations/ko.json +1 -1
  190. homeassistant/components/sleepiq/translations/ko.json +1 -1
  191. homeassistant/components/sma/config_flow.py +30 -1
  192. homeassistant/components/smarla/translations/it.json +9 -0
  193. homeassistant/components/smarttub/translations/ko.json +1 -1
  194. homeassistant/components/sonarr/translations/ko.json +1 -1
  195. homeassistant/components/sonos/translations/lt.json +9 -0
  196. homeassistant/components/spotify/translations/ko.json +1 -1
  197. homeassistant/components/sql/translations/it.json +1 -0
  198. homeassistant/components/squeezebox/translations/it.json +5 -0
  199. homeassistant/components/steam_online/translations/ko.json +1 -1
  200. homeassistant/components/subaru/translations/lt.json +2 -0
  201. homeassistant/components/subaru/translations/mk.json +12 -0
  202. homeassistant/components/surepetcare/translations/ko.json +1 -1
  203. homeassistant/components/switchbot/translations/en-GB.json +5 -0
  204. homeassistant/components/switchbot/translations/es.json +1 -0
  205. homeassistant/components/switchbot/translations/et.json +1 -0
  206. homeassistant/components/switchbot/translations/ga.json +13 -0
  207. homeassistant/components/switchbot/translations/lt.json +54 -0
  208. homeassistant/components/switchbot/translations/nl.json +1 -0
  209. homeassistant/components/switchbot/translations/sk.json +1 -0
  210. homeassistant/components/switchbot/translations/zh-Hans.json +1 -0
  211. homeassistant/components/switchbot/translations/zh-Hant.json +1 -0
  212. homeassistant/components/telegram_bot/translations/bg.json +10 -0
  213. homeassistant/components/telegram_bot/translations/ga.json +28 -0
  214. homeassistant/components/telegram_bot/translations/it.json +12 -0
  215. homeassistant/components/telegram_bot/translations/lt.json +30 -0
  216. homeassistant/components/telegram_bot/translations/mk.json +5 -0
  217. homeassistant/components/template/translations/it.json +1 -0
  218. homeassistant/components/tesla_fleet/translations/en-GB.json +3 -0
  219. homeassistant/components/tesla_fleet/translations/et.json +3 -0
  220. homeassistant/components/tesla_fleet/translations/lt.json +3 -0
  221. homeassistant/components/tesla_fleet/translations/mk.json +3 -0
  222. homeassistant/components/tesla_fleet/translations/nl.json +3 -0
  223. homeassistant/components/tesla_fleet/translations/zh-Hans.json +3 -0
  224. homeassistant/components/tessie/translations/ko.json +1 -1
  225. homeassistant/components/thermopro/manifest.json +1 -1
  226. homeassistant/components/thermopro/translations/lt.json +7 -0
  227. homeassistant/components/timer/translations/ko.json +2 -1
  228. homeassistant/components/totalconnect/translations/ko.json +1 -1
  229. homeassistant/components/transmission/translations/ko.json +1 -1
  230. homeassistant/components/tuya/translations/it.json +13 -0
  231. homeassistant/components/twitch/translations/ko.json +1 -1
  232. homeassistant/components/unifiprotect/config_flow.py +1 -1
  233. homeassistant/components/uptimerobot/translations/it.json +5 -0
  234. homeassistant/components/uptimerobot/translations/ko.json +1 -1
  235. homeassistant/components/watttime/translations/ko.json +1 -1
  236. homeassistant/components/weatherflow_cloud/translations/ga.json +1 -0
  237. homeassistant/components/webdav/translations/et.json +1 -0
  238. homeassistant/components/webdav/translations/ga.json +1 -0
  239. homeassistant/components/webdav/translations/lt.json +1 -0
  240. homeassistant/components/webdav/translations/mk.json +7 -0
  241. homeassistant/components/webdav/translations/zh-Hans.json +1 -0
  242. homeassistant/components/whirlpool/translations/et.json +1 -0
  243. homeassistant/components/whirlpool/translations/lt.json +1 -0
  244. homeassistant/components/whirlpool/translations/zh-Hans.json +1 -0
  245. homeassistant/components/withings/translations/it.json +3 -0
  246. homeassistant/components/withings/translations/ko.json +1 -1
  247. homeassistant/components/xiaomi_miio/translations/ko.json +1 -1
  248. homeassistant/components/yolink/translations/ko.json +1 -1
  249. homeassistant/components/youtube/translations/ko.json +1 -1
  250. homeassistant/components/zha/translations/it.json +10 -2
  251. homeassistant/components/zha/translations/lt.json +50 -0
  252. homeassistant/components/zwave_js/strings.json +11 -11
  253. homeassistant/components/zwave_js/translations/bg.json +6 -20
  254. homeassistant/components/zwave_js/translations/cs.json +0 -22
  255. homeassistant/components/zwave_js/translations/de.json +0 -23
  256. homeassistant/components/zwave_js/translations/el.json +0 -23
  257. homeassistant/components/zwave_js/translations/en-GB.json +0 -23
  258. homeassistant/components/zwave_js/translations/en.json +11 -11
  259. homeassistant/components/zwave_js/translations/es.json +0 -23
  260. homeassistant/components/zwave_js/translations/et.json +0 -23
  261. homeassistant/components/zwave_js/translations/fi.json +0 -11
  262. homeassistant/components/zwave_js/translations/ga.json +16 -22
  263. homeassistant/components/zwave_js/translations/hu.json +0 -23
  264. homeassistant/components/zwave_js/translations/it.json +0 -12
  265. homeassistant/components/zwave_js/translations/lt.json +32 -22
  266. homeassistant/components/zwave_js/translations/mk.json +16 -7
  267. homeassistant/components/zwave_js/translations/pt.json +0 -23
  268. homeassistant/components/zwave_js/translations/ru.json +0 -21
  269. homeassistant/components/zwave_js/translations/sk.json +0 -23
  270. homeassistant/components/zwave_js/translations/sv.json +0 -23
  271. homeassistant/components/zwave_js/translations/tr.json +0 -13
  272. homeassistant/components/zwave_js/translations/zh-Hans.json +0 -23
  273. homeassistant/components/zwave_js/translations/zh-Hant.json +0 -23
  274. homeassistant/const.py +1 -1
  275. homeassistant/package_constraints.txt +2 -2
  276. {homeassistant-2025.7.0b7.dist-info → homeassistant-2025.7.0b9.dist-info}/METADATA +1 -1
  277. {homeassistant-2025.7.0b7.dist-info → homeassistant-2025.7.0b9.dist-info}/RECORD +282 -277
  278. homeassistant/components/zwave_js/translations/sq.json +0 -7
  279. {homeassistant-2025.7.0b7.dist-info → homeassistant-2025.7.0b9.dist-info}/WHEEL +0 -0
  280. {homeassistant-2025.7.0b7.dist-info → homeassistant-2025.7.0b9.dist-info}/entry_points.txt +0 -0
  281. {homeassistant-2025.7.0b7.dist-info → homeassistant-2025.7.0b9.dist-info}/licenses/LICENSE.md +0 -0
  282. {homeassistant-2025.7.0b7.dist-info → homeassistant-2025.7.0b9.dist-info}/licenses/homeassistant/backports/LICENSE.Python +0 -0
  283. {homeassistant-2025.7.0b7.dist-info → homeassistant-2025.7.0b9.dist-info}/top_level.txt +0 -0
@@ -22,7 +22,7 @@ from homeassistant.config_entries import (
22
22
  )
23
23
  from homeassistant.const import CONF_LLM_HASS_API, CONF_NAME, CONF_URL
24
24
  from homeassistant.core import HomeAssistant, callback
25
- from homeassistant.helpers import llm
25
+ from homeassistant.helpers import config_validation as cv, llm
26
26
  from homeassistant.helpers.selector import (
27
27
  BooleanSelector,
28
28
  NumberSelector,
@@ -38,6 +38,7 @@ from homeassistant.helpers.selector import (
38
38
  )
39
39
  from homeassistant.util.ssl import get_default_context
40
40
 
41
+ from . import OllamaConfigEntry
41
42
  from .const import (
42
43
  CONF_KEEP_ALIVE,
43
44
  CONF_MAX_HISTORY,
@@ -72,43 +73,43 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
72
73
  class OllamaConfigFlow(ConfigFlow, domain=DOMAIN):
73
74
  """Handle a config flow for Ollama."""
74
75
 
75
- VERSION = 2
76
- MINOR_VERSION = 2
76
+ VERSION = 3
77
+ MINOR_VERSION = 1
77
78
 
78
79
  def __init__(self) -> None:
79
80
  """Initialize config flow."""
80
81
  self.url: str | None = None
81
- self.model: str | None = None
82
- self.client: ollama.AsyncClient | None = None
83
- self.download_task: asyncio.Task | None = None
84
82
 
85
83
  async def async_step_user(
86
84
  self, user_input: dict[str, Any] | None = None
87
85
  ) -> ConfigFlowResult:
88
86
  """Handle the initial step."""
89
- user_input = user_input or {}
90
- self.url = user_input.get(CONF_URL, self.url)
91
- self.model = user_input.get(CONF_MODEL, self.model)
92
-
93
- if self.url is None:
87
+ if user_input is None:
94
88
  return self.async_show_form(
95
- step_id="user", data_schema=STEP_USER_DATA_SCHEMA, last_step=False
89
+ step_id="user", data_schema=STEP_USER_DATA_SCHEMA
96
90
  )
97
91
 
98
92
  errors = {}
93
+ url = user_input[CONF_URL]
99
94
 
100
- self._async_abort_entries_match({CONF_URL: self.url})
95
+ self._async_abort_entries_match({CONF_URL: url})
101
96
 
102
97
  try:
103
- self.client = ollama.AsyncClient(
104
- host=self.url, verify=get_default_context()
98
+ url = cv.url(url)
99
+ except vol.Invalid:
100
+ errors["base"] = "invalid_url"
101
+ return self.async_show_form(
102
+ step_id="user",
103
+ data_schema=self.add_suggested_values_to_schema(
104
+ STEP_USER_DATA_SCHEMA, user_input
105
+ ),
106
+ errors=errors,
105
107
  )
106
- async with asyncio.timeout(DEFAULT_TIMEOUT):
107
- response = await self.client.list()
108
108
 
109
- downloaded_models: set[str] = {
110
- model_info["model"] for model_info in response.get("models", [])
111
- }
109
+ try:
110
+ client = ollama.AsyncClient(host=url, verify=get_default_context())
111
+ async with asyncio.timeout(DEFAULT_TIMEOUT):
112
+ await client.list()
112
113
  except (TimeoutError, httpx.ConnectError):
113
114
  errors["base"] = "cannot_connect"
114
115
  except Exception:
@@ -117,10 +118,69 @@ class OllamaConfigFlow(ConfigFlow, domain=DOMAIN):
117
118
 
118
119
  if errors:
119
120
  return self.async_show_form(
120
- step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
121
+ step_id="user",
122
+ data_schema=self.add_suggested_values_to_schema(
123
+ STEP_USER_DATA_SCHEMA, user_input
124
+ ),
125
+ errors=errors,
121
126
  )
122
127
 
123
- if self.model is None:
128
+ return self.async_create_entry(
129
+ title=url,
130
+ data={CONF_URL: url},
131
+ )
132
+
133
+ @classmethod
134
+ @callback
135
+ def async_get_supported_subentry_types(
136
+ cls, config_entry: ConfigEntry
137
+ ) -> dict[str, type[ConfigSubentryFlow]]:
138
+ """Return subentries supported by this integration."""
139
+ return {"conversation": ConversationSubentryFlowHandler}
140
+
141
+
142
+ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
143
+ """Flow for managing conversation subentries."""
144
+
145
+ def __init__(self) -> None:
146
+ """Initialize the subentry flow."""
147
+ super().__init__()
148
+ self._name: str | None = None
149
+ self._model: str | None = None
150
+ self.download_task: asyncio.Task | None = None
151
+ self._config_data: dict[str, Any] | None = None
152
+
153
+ @property
154
+ def _is_new(self) -> bool:
155
+ """Return if this is a new subentry."""
156
+ return self.source == "user"
157
+
158
+ @property
159
+ def _client(self) -> ollama.AsyncClient:
160
+ """Return the Ollama client."""
161
+ entry: OllamaConfigEntry = self._get_entry()
162
+ return entry.runtime_data
163
+
164
+ async def async_step_set_options(
165
+ self, user_input: dict[str, Any] | None = None
166
+ ) -> SubentryFlowResult:
167
+ """Handle model selection and configuration step."""
168
+ if self._get_entry().state != ConfigEntryState.LOADED:
169
+ return self.async_abort(reason="entry_not_loaded")
170
+
171
+ if user_input is None:
172
+ # Get available models from Ollama server
173
+ try:
174
+ async with asyncio.timeout(DEFAULT_TIMEOUT):
175
+ response = await self._client.list()
176
+
177
+ downloaded_models: set[str] = {
178
+ model_info["model"] for model_info in response.get("models", [])
179
+ }
180
+ except (TimeoutError, httpx.ConnectError, httpx.HTTPError):
181
+ _LOGGER.exception("Failed to get models from Ollama server")
182
+ return self.async_abort(reason="cannot_connect")
183
+
124
184
  # Show models that have been downloaded first, followed by all known
125
185
  # models (only latest tags).
126
186
  models_to_list = [
@@ -131,52 +191,69 @@ class OllamaConfigFlow(ConfigFlow, domain=DOMAIN):
131
191
  for m in sorted(MODEL_NAMES)
132
192
  if m not in downloaded_models
133
193
  ]
134
- model_step_schema = vol.Schema(
135
- {
136
- vol.Required(
137
- CONF_MODEL, description={"suggested_value": DEFAULT_MODEL}
138
- ): SelectSelector(
139
- SelectSelectorConfig(options=models_to_list, custom_value=True)
140
- ),
141
- }
142
- )
194
+
195
+ if self._is_new:
196
+ options = {}
197
+ else:
198
+ options = self._get_reconfigure_subentry().data.copy()
143
199
 
144
200
  return self.async_show_form(
145
- step_id="user",
146
- data_schema=model_step_schema,
201
+ step_id="set_options",
202
+ data_schema=vol.Schema(
203
+ ollama_config_option_schema(
204
+ self.hass, self._is_new, options, models_to_list
205
+ )
206
+ ),
147
207
  )
148
208
 
149
- if self.model not in downloaded_models:
150
- # Ollama server needs to download model first
151
- return await self.async_step_download()
209
+ self._model = user_input[CONF_MODEL]
210
+ if self._is_new:
211
+ self._name = user_input.pop(CONF_NAME)
152
212
 
153
- return self.async_create_entry(
154
- title=self.url,
155
- data={CONF_URL: self.url, CONF_MODEL: self.model},
156
- subentries=[
157
- {
158
- "subentry_type": "conversation",
159
- "data": {},
160
- "title": _get_title(self.model),
161
- "unique_id": None,
162
- }
163
- ],
213
+ # Check if model needs to be downloaded
214
+ try:
215
+ async with asyncio.timeout(DEFAULT_TIMEOUT):
216
+ response = await self._client.list()
217
+
218
+ currently_downloaded_models: set[str] = {
219
+ model_info["model"] for model_info in response.get("models", [])
220
+ }
221
+
222
+ if self._model not in currently_downloaded_models:
223
+ # Store the user input to use after download
224
+ self._config_data = user_input
225
+ # Ollama server needs to download model first
226
+ return await self.async_step_download()
227
+ except Exception:
228
+ _LOGGER.exception("Failed to check model availability")
229
+ return self.async_abort(reason="cannot_connect")
230
+
231
+ # Model is already downloaded, create/update the entry
232
+ if self._is_new:
233
+ return self.async_create_entry(
234
+ title=self._name,
235
+ data=user_input,
236
+ )
237
+
238
+ return self.async_update_and_abort(
239
+ self._get_entry(),
240
+ self._get_reconfigure_subentry(),
241
+ data=user_input,
164
242
  )
165
243
 
166
244
  async def async_step_download(
167
245
  self, user_input: dict[str, Any] | None = None
168
- ) -> ConfigFlowResult:
246
+ ) -> SubentryFlowResult:
169
247
  """Step to wait for Ollama server to download a model."""
170
- assert self.model is not None
171
- assert self.client is not None
248
+ assert self._model is not None
172
249
 
173
250
  if self.download_task is None:
174
251
  # Tell Ollama server to pull the model.
175
252
  # The task will block until the model and metadata are fully
176
253
  # downloaded.
177
254
  self.download_task = self.hass.async_create_background_task(
178
- self.client.pull(self.model),
179
- f"Downloading {self.model}",
255
+ self._client.pull(self._model),
256
+ f"Downloading {self._model}",
180
257
  )
181
258
 
182
259
  if self.download_task.done():
@@ -192,80 +269,28 @@ class OllamaConfigFlow(ConfigFlow, domain=DOMAIN):
192
269
  progress_task=self.download_task,
193
270
  )
194
271
 
195
- async def async_step_finish(
196
- self, user_input: dict[str, Any] | None = None
197
- ) -> ConfigFlowResult:
198
- """Step after model downloading has succeeded."""
199
- assert self.url is not None
200
- assert self.model is not None
201
-
202
- return self.async_create_entry(
203
- title=_get_title(self.model),
204
- data={CONF_URL: self.url, CONF_MODEL: self.model},
205
- subentries=[
206
- {
207
- "subentry_type": "conversation",
208
- "data": {},
209
- "title": _get_title(self.model),
210
- "unique_id": None,
211
- }
212
- ],
213
- )
214
-
215
272
  async def async_step_failed(
216
273
  self, user_input: dict[str, Any] | None = None
217
- ) -> ConfigFlowResult:
274
+ ) -> SubentryFlowResult:
218
275
  """Step after model downloading has failed."""
219
276
  return self.async_abort(reason="download_failed")
220
277
 
221
- @classmethod
222
- @callback
223
- def async_get_supported_subentry_types(
224
- cls, config_entry: ConfigEntry
225
- ) -> dict[str, type[ConfigSubentryFlow]]:
226
- """Return subentries supported by this integration."""
227
- return {"conversation": ConversationSubentryFlowHandler}
228
-
229
-
230
- class ConversationSubentryFlowHandler(ConfigSubentryFlow):
231
- """Flow for managing conversation subentries."""
232
-
233
- @property
234
- def _is_new(self) -> bool:
235
- """Return if this is a new subentry."""
236
- return self.source == "user"
237
-
238
- async def async_step_set_options(
278
+ async def async_step_finish(
239
279
  self, user_input: dict[str, Any] | None = None
240
280
  ) -> SubentryFlowResult:
241
- """Set conversation options."""
242
- # abort if entry is not loaded
243
- if self._get_entry().state != ConfigEntryState.LOADED:
244
- return self.async_abort(reason="entry_not_loaded")
245
-
246
- errors: dict[str, str] = {}
247
-
248
- if user_input is None:
249
- if self._is_new:
250
- options = {}
251
- else:
252
- options = self._get_reconfigure_subentry().data.copy()
281
+ """Step after model downloading has succeeded."""
282
+ assert self._config_data is not None
253
283
 
254
- elif self._is_new:
284
+ # Model download completed, create/update the entry with stored config
285
+ if self._is_new:
255
286
  return self.async_create_entry(
256
- title=user_input.pop(CONF_NAME),
257
- data=user_input,
287
+ title=self._name,
288
+ data=self._config_data,
258
289
  )
259
- else:
260
- return self.async_update_and_abort(
261
- self._get_entry(),
262
- self._get_reconfigure_subentry(),
263
- data=user_input,
264
- )
265
-
266
- schema = ollama_config_option_schema(self.hass, self._is_new, options)
267
- return self.async_show_form(
268
- step_id="set_options", data_schema=vol.Schema(schema), errors=errors
290
+ return self.async_update_and_abort(
291
+ self._get_entry(),
292
+ self._get_reconfigure_subentry(),
293
+ data=self._config_data,
269
294
  )
270
295
 
271
296
  async_step_user = async_step_set_options
@@ -273,19 +298,14 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
273
298
 
274
299
 
275
300
  def ollama_config_option_schema(
276
- hass: HomeAssistant, is_new: bool, options: Mapping[str, Any]
301
+ hass: HomeAssistant,
302
+ is_new: bool,
303
+ options: Mapping[str, Any],
304
+ models_to_list: list[SelectOptionDict],
277
305
  ) -> dict:
278
306
  """Ollama options schema."""
279
- hass_apis: list[SelectOptionDict] = [
280
- SelectOptionDict(
281
- label=api.name,
282
- value=api.id,
283
- )
284
- for api in llm.async_get_apis(hass)
285
- ]
286
-
287
307
  if is_new:
288
- schema: dict[vol.Required | vol.Optional, Any] = {
308
+ schema: dict = {
289
309
  vol.Required(CONF_NAME, default="Ollama Conversation"): str,
290
310
  }
291
311
  else:
@@ -293,6 +313,12 @@ def ollama_config_option_schema(
293
313
 
294
314
  schema.update(
295
315
  {
316
+ vol.Required(
317
+ CONF_MODEL,
318
+ description={"suggested_value": options.get(CONF_MODEL, DEFAULT_MODEL)},
319
+ ): SelectSelector(
320
+ SelectSelectorConfig(options=models_to_list, custom_value=True)
321
+ ),
296
322
  vol.Optional(
297
323
  CONF_PROMPT,
298
324
  description={
@@ -304,7 +330,18 @@ def ollama_config_option_schema(
304
330
  vol.Optional(
305
331
  CONF_LLM_HASS_API,
306
332
  description={"suggested_value": options.get(CONF_LLM_HASS_API)},
307
- ): SelectSelector(SelectSelectorConfig(options=hass_apis, multiple=True)),
333
+ ): SelectSelector(
334
+ SelectSelectorConfig(
335
+ options=[
336
+ SelectOptionDict(
337
+ label=api.name,
338
+ value=api.id,
339
+ )
340
+ for api in llm.async_get_apis(hass)
341
+ ],
342
+ multiple=True,
343
+ )
344
+ ),
308
345
  vol.Optional(
309
346
  CONF_NUM_CTX,
310
347
  description={
@@ -350,11 +387,3 @@ def ollama_config_option_schema(
350
387
  )
351
388
 
352
389
  return schema
353
-
354
-
355
- def _get_title(model: str) -> str:
356
- """Get title for config entry."""
357
- if model.endswith(":latest"):
358
- model = model.split(":", maxsplit=1)[0]
359
-
360
- return model