|
1 | | -package com.google.ai.sample.feature.chat |
2 | | - |
3 | | -import androidx.lifecycle.ViewModel |
4 | | -import androidx.lifecycle.viewModelScope |
5 | | -import com.google.ai.client.generativeai.GenerativeModel |
6 | | -import com.google.ai.client.generativeai.type.asTextOrNull |
7 | | -import com.google.ai.client.generativeai.type.content |
8 | | -import com.google.ai.client.generativeai.type.generationConfig // Import hinzugefügt |
9 | | -import com.google.ai.sample.BuildConfig // Import hinzugefügt |
10 | | -import com.google.ai.sample.MainActivity |
11 | | -import com.google.ai.sample.ScreenOperatorAccessibilityService |
12 | | -import com.google.ai.sample.util.Command |
13 | | -import com.google.ai.sample.util.CommandParser |
14 | | -import kotlinx.coroutines.Dispatchers |
15 | | -import kotlinx.coroutines.flow.MutableStateFlow |
16 | | -import kotlinx.coroutines.flow.StateFlow |
17 | | -import kotlinx.coroutines.flow.asStateFlow |
18 | | -import kotlinx.coroutines.launch |
19 | | -import android.util.Log |
20 | | - |
21 | | -class ChatViewModel( |
22 | | - // Das initiale Modell wird weiterhin übergeben |
23 | | - initialGenerativeModel: GenerativeModel |
24 | | -) : ViewModel() { |
25 | | - private val TAG = "ChatViewModel" |
26 | | - |
27 | | - // --- NEU: Variable für das aktuell verwendete Modell --- |
28 | | - private var currentGenerativeModel: GenerativeModel = initialGenerativeModel |
29 | | - private set // Nur intern änderbar |
30 | | - |
31 | | - // --- GEÄNDERT: Chat wird mit dem aktuellen Modell initialisiert --- |
32 | | - private var chat = currentGenerativeModel.startChat( |
33 | | - history = listOf( |
34 | | - content(role = "user") { text("Hello, I have 2 dogs in my house.") }, |
35 | | - content(role = "model") { text("Great to meet you. What would you like to know?") } |
36 | | - ) |
37 | | - ) |
38 | | - |
39 | | - private val _uiState: MutableStateFlow<ChatUiState> = |
40 | | - MutableStateFlow(ChatUiState(chat.history.map { content -> |
41 | | - // Map the initial messages |
42 | | - ChatMessage( |
43 | | - text = content.parts.first().asTextOrNull() ?: "", |
44 | | - participant = if (content.role == "user") Participant.USER else Participant.MODEL, |
45 | | - isPending = false |
46 | | - ) |
47 | | - })) |
48 | | - val uiState: StateFlow<ChatUiState> = |
49 | | - _uiState.asStateFlow() |
50 | | - |
51 | | - // Keep track of detected commands |
52 | | - private val _detectedCommands = MutableStateFlow<List<Command>>(emptyList()) |
53 | | - val detectedCommands: StateFlow<List<Command>> = _detectedCommands.asStateFlow() |
54 | | - |
55 | | - // Keep track of command execution status |
56 | | - private val _commandExecutionStatus = MutableStateFlow<String>("") |
57 | | - val commandExecutionStatus: StateFlow<String> = _commandExecutionStatus.asStateFlow() |
58 | | - |
59 | | - fun sendMessage(userMessage: String) { |
60 | | - // Add a pending message |
61 | | - _uiState.value.addMessage( |
62 | | - ChatMessage( |
63 | | - text = userMessage, |
64 | | - participant = Participant.USER, |
65 | | - isPending = true |
66 | | - ) |
67 | | - ) |
68 | | - |
69 | | - // Clear previous commands |
70 | | - _detectedCommands.value = emptyList() |
71 | | - _commandExecutionStatus.value = "" |
72 | | - |
73 | | - viewModelScope.launch { |
74 | | - try { |
75 | | - // --- GEÄNDERT: Verwendet die aktuelle 'chat'-Instanz --- |
76 | | - val response = chat.sendMessage(userMessage) |
77 | | - |
78 | | - _uiState.value.replaceLastPendingMessage() |
79 | | - |
80 | | - response.text?.let { modelResponse -> |
81 | | - _uiState.value.addMessage( |
82 | | - ChatMessage( |
83 | | - text = modelResponse, |
84 | | - participant = Participant.MODEL, |
85 | | - isPending = false |
86 | | - ) |
87 | | - ) |
88 | | - |
89 | | - // Process commands in the response |
90 | | - processCommands(modelResponse) |
91 | | - } |
92 | | - } catch (e: Exception) { |
93 | | - Log.e(TAG, "Error sending message: ${e.message}", e) // Logging hinzugefügt |
94 | | - _uiState.value.replaceLastPendingMessage() |
95 | | - _uiState.value.addMessage( |
96 | | - ChatMessage( |
97 | | - text = e.localizedMessage ?: "Unknown error sending message", // Klarere Fehlermeldung |
98 | | - participant = Participant.ERROR |
99 | | - ) |
100 | | - ) |
101 | | - |
102 | | - // Update command execution status |
103 | | - _commandExecutionStatus.value = "Fehler beim Senden: ${e.localizedMessage}" |
104 | | - } |
105 | | - } |
106 | | - } |
107 | | - |
108 | | - /** |
109 | | - * Process commands found in the AI response |
110 | | - */ |
111 | | - private fun processCommands(text: String) { |
112 | | - viewModelScope.launch(Dispatchers.Main) { |
113 | | - try { |
114 | | - // Parse commands from the text |
115 | | - // --- HINWEIS: Die Logs im Parser bleiben erhalten --- |
116 | | - val commands = CommandParser.parseCommands(text) |
117 | | - |
118 | | - if (commands.isNotEmpty()) { |
119 | | - Log.d(TAG, "Found ${commands.size} commands in response") |
120 | | - |
121 | | - // Update the detected commands |
122 | | - val currentCommands = _detectedCommands.value.toMutableList() |
123 | | - currentCommands.addAll(commands) |
124 | | - _detectedCommands.value = currentCommands |
125 | | - |
126 | | - // Update status to show commands were detected |
127 | | - val commandDescriptions = commands.map { |
128 | | - when (it) { |
129 | | - is Command.ClickButton -> "Klick auf Button: \"${it.buttonText}\"" |
130 | | - is Command.TapCoordinates -> "Tippen auf Koordinaten: (${it.x}, ${it.y})" |
131 | | - is Command.TakeScreenshot -> "Screenshot aufnehmen" |
132 | | - is Command.PressHomeButton -> "Home-Button drücken" |
133 | | - is Command.PressBackButton -> "Zurück-Button drücken" |
134 | | - is Command.ShowRecentApps -> "Übersicht der letzten Apps öffnen" |
135 | | - is Command.ScrollDown -> "Nach unten scrollen" |
136 | | - is Command.ScrollUp -> "Nach oben scrollen" |
137 | | - is Command.ScrollLeft -> "Nach links scrollen" |
138 | | - is Command.ScrollRight -> "Nach rechts scrollen" |
139 | | - is Command.ScrollDownFromCoordinates -> "Nach unten scrollen von Position (${it.x}, ${it.y}) mit Distanz ${it.distance}px und Dauer ${it.duration}ms" |
140 | | - is Command.ScrollUpFromCoordinates -> "Nach oben scrollen von Position (${it.x}, ${it.y}) mit Distanz ${it.distance}px und Dauer ${it.duration}ms" |
141 | | - is Command.ScrollLeftFromCoordinates -> "Nach links scrollen von Position (${it.x}, ${it.y}) mit Distanz ${it.distance}px und Dauer ${it.duration}ms" |
142 | | - is Command.ScrollRightFromCoordinates -> "Nach rechts scrollen von Position (${it.x}, ${it.y}) mit Distanz ${it.distance}px und Dauer ${it.duration}ms" |
143 | | - is Command.OpenApp -> "App öffnen: \"${it.packageName}\"" |
144 | | - is Command.WriteText -> "Text schreiben: \"${it.text}\"" |
145 | | - is Command.UseHighReasoningModel -> "Wechsle zu leistungsfähigerem Modell (gemini-2.5-pro-exp-03-25)" // Name aktualisiert |
146 | | - is Command.UseLowReasoningModel -> "Wechsle zu schnellerem Modell (gemini-2.0-flash-lite)" |
147 | | - } |
148 | | - } |
149 | | - |
150 | | - // Show toast with detected commands |
151 | | - val mainActivity = MainActivity.getInstance() |
152 | | - mainActivity?.updateStatusMessage( |
153 | | - "Befehle erkannt: ${commandDescriptions.joinToString(", ")}", |
154 | | - false |
155 | | - ) |
156 | | - |
157 | | - // Update status |
158 | | - _commandExecutionStatus.value = "Befehle erkannt: ${commandDescriptions.joinToString(", ")}" |
159 | | - |
160 | | - // Check if accessibility service is enabled |
161 | | - val isServiceEnabled = mainActivity?.let { |
162 | | - ScreenOperatorAccessibilityService.isAccessibilityServiceEnabled(it) |
163 | | - } ?: false |
164 | | - |
165 | | - if (!isServiceEnabled) { |
166 | | - Log.e(TAG, "Accessibility service is not enabled") |
167 | | - _commandExecutionStatus.value = "Accessibility Service ist nicht aktiviert. Bitte aktivieren Sie den Service in den Einstellungen." |
168 | | - |
169 | | - // Prompt user to enable accessibility service |
170 | | - mainActivity?.checkAccessibilityServiceEnabled() |
171 | | - return@launch |
172 | | - } |
173 | | - |
174 | | - // Check if service is available |
175 | | - if (!ScreenOperatorAccessibilityService.isServiceAvailable()) { |
176 | | - Log.e(TAG, "Accessibility service is not available") |
177 | | - _commandExecutionStatus.value = "Accessibility Service ist nicht verfügbar. Bitte starten Sie die App neu." |
178 | | - |
179 | | - // Show toast |
180 | | - mainActivity?.updateStatusMessage( |
181 | | - "Accessibility Service ist nicht verfügbar. Bitte starten Sie die App neu.", |
182 | | - true |
183 | | - ) |
184 | | - return@launch |
185 | | - } |
186 | | - |
187 | | - // Execute each command |
188 | | - commands.forEachIndexed { index, command -> |
189 | | - Log.d(TAG, "Executing command: $command") |
190 | | - |
191 | | - // Update status to show command is being executed |
192 | | - val commandDescription = when (command) { |
193 | | - is Command.ClickButton -> "Klick auf Button: \"${command.buttonText}\"" |
194 | | - is Command.TapCoordinates -> "Tippen auf Koordinaten: (${command.x}, ${command.y})" |
195 | | - is Command.TakeScreenshot -> "Screenshot aufnehmen" |
196 | | - is Command.PressHomeButton -> "Home-Button drücken" |
197 | | - is Command.PressBackButton -> "Zurück-Button drücken" |
198 | | - is Command.ShowRecentApps -> "Übersicht der letzten Apps öffnen" |
199 | | - is Command.ScrollDown -> "Nach unten scrollen" |
200 | | - is Command.ScrollUp -> "Nach oben scrollen" |
201 | | - is Command.ScrollLeft -> "Nach links scrollen" |
202 | | - is Command.ScrollRight -> "Nach rechts scrollen" |
203 | | - is Command.ScrollDownFromCoordinates -> "Nach unten scrollen von Position (${command.x}, ${command.y}) mit Distanz ${command.distance}px und Dauer ${command.duration}ms" |
204 | | - is Command.ScrollUpFromCoordinates -> "Nach oben scrollen von Position (${command.x}, ${command.y}) mit Distanz ${command.distance}px und Dauer ${command.duration}ms" |
205 | | - is Command.ScrollLeftFromCoordinates -> "Nach links scrollen von Position (${command.x}, ${command.y}) mit Distanz ${command.distance}px und Dauer ${command.duration}ms" |
206 | | - is Command.ScrollRightFromCoordinates -> "Nach rechts scrollen von Position (${command.x}, ${command.y}) mit Distanz ${command.distance}px und Dauer ${command.duration}ms" |
207 | | - is Command.OpenApp -> "App öffnen: \"${command.packageName}\"" |
208 | | - is Command.WriteText -> "Text schreiben: \"${command.text}\"" |
209 | | - is Command.UseHighReasoningModel -> "Wechsle zu leistungsfähigerem Modell (gemini-2.5-pro-exp-03-25)" // Name aktualisiert |
210 | | - is Command.UseLowReasoningModel -> "Wechsle zu schnellerem Modell (gemini-2.0-flash-lite)" |
211 | | - } |
212 | | - |
213 | | - _commandExecutionStatus.value = "Führe aus: $commandDescription (${index + 1}/${commands.size})" |
214 | | - |
215 | | - // Show toast with command being executed |
216 | | - mainActivity?.updateStatusMessage( |
217 | | - "Führe aus: $commandDescription", |
218 | | - false |
219 | | - ) |
220 | | - |
221 | | - // Execute the command |
222 | | - ScreenOperatorAccessibilityService.executeCommand(command) |
223 | | - |
224 | | - // Add a small delay between commands to avoid overwhelming the system |
225 | | - kotlinx.coroutines.delay(800) |
226 | | - } |
227 | | - |
228 | | - // Update status to show all commands were executed |
229 | | - _commandExecutionStatus.value = "Alle Befehle ausgeführt: ${commandDescriptions.joinToString(", ")}" |
230 | | - |
231 | | - // Show toast with all commands executed |
232 | | - mainActivity?.updateStatusMessage( |
233 | | - "Alle Befehle ausgeführt", |
234 | | - false |
235 | | - ) |
236 | | - } |
237 | | - } catch (e: Exception) { |
238 | | - Log.e(TAG, "Error processing commands: ${e.message}", e) |
239 | | - _commandExecutionStatus.value = "Fehler bei der Befehlsverarbeitung: ${e.message}" |
240 | | - |
241 | | - // Show toast with error |
242 | | - val mainActivity = MainActivity.getInstance() |
243 | | - mainActivity?.updateStatusMessage( |
244 | | - "Fehler bei der Befehlsverarbeitung: ${e.message}", |
245 | | - true |
246 | | - ) |
247 | | - } |
248 | | - } |
249 | | - } |
250 | | - |
251 | | - // --- NEUE FUNKTION zum Aktualisieren des Modells --- |
252 | | - /** |
253 | | - * Updates the GenerativeModel instance used by this ViewModel and restarts the chat. |
254 | | - * |
255 | | - * @param newModelName The name of the new model to use (e.g., "gemini-2.5-pro-exp-03-25"). |
256 | | - */ |
257 | | - fun updateGenerativeModel(newModelName: String) { |
258 | | - viewModelScope.launch { |
259 | | - Log.i(TAG, "Updating GenerativeModel to: $newModelName") |
260 | | - try { |
261 | | - val config = generationConfig { |
262 | | - temperature = 0.0f // Behalte deine Standardkonfiguration bei |
263 | | - // Weitere Konfigurationen hier hinzufügen, falls nötig |
264 | | - } |
265 | | - // Erstelle eine NEUE Instanz mit dem neuen Namen |
266 | | - currentGenerativeModel = GenerativeModel( |
267 | | - modelName = newModelName, |
268 | | - apiKey = BuildConfig.apiKey, // Stelle sicher, dass apiKey hier verfügbar ist |
269 | | - generationConfig = config |
270 | | - ) |
271 | | - |
272 | | - // Speichere die aktuelle History, bevor der Chat neu gestartet wird |
273 | | - val currentHistory = chat.history |
274 | | - |
275 | | - // Initialisiere die 'chat'-Instanz neu, damit sie das neue Modell verwendet |
276 | | - chat = currentGenerativeModel.startChat( |
277 | | - history = currentHistory // Übernehme die bisherige History |
278 | | - ) |
279 | | - Log.i(TAG, "GenerativeModel and chat instance updated successfully. History preserved.") |
280 | | - |
281 | | - // Optional: Füge eine Nachricht zum Chat hinzu, um den Wechsel anzuzeigen |
282 | | - _uiState.value.addMessage( |
283 | | - ChatMessage( |
284 | | - text = "Chat-Modell wurde zu '$newModelName' gewechselt.", |
285 | | - participant = Participant.MODEL, // Oder eine neue Participant.SYSTEM Rolle |
286 | | - isPending = false |
287 | | - ) |
288 | | - ) |
289 | | - |
290 | | - } catch (e: Exception) { |
291 | | - Log.e(TAG, "Failed to update GenerativeModel to $newModelName: ${e.message}", e) |
292 | | - // Optional: Füge eine Fehlermeldung zum Chat hinzu |
293 | | - _uiState.value.addMessage( |
294 | | - ChatMessage( |
295 | | - text = "Fehler beim Wechseln des Chat-Modells zu '$newModelName': ${e.localizedMessage}", |
296 | | - participant = Participant.ERROR, |
297 | | - isPending = false |
298 | | - ) |
299 | | - ) |
300 | | - _commandExecutionStatus.value = "Fehler beim Modellwechsel: ${e.localizedMessage}" |
301 | | - } |
302 | | - } |
303 | | - } |
304 | | -} |
305 | | -``` |
306 | | - |
307 | | ---- END OF FILE ChatViewModel.kt.txt --- |
308 | | - |
309 | | ---- START OF FILE PhotoReasoningViewModel.kt.txt --- |
310 | | - |
311 | | -```kotlin |
312 | | - |
313 | | -``` |
314 | | - |
315 | | ---- END OF FILE PhotoReasoningViewModel.kt.txt --- |
316 | | - |
317 | | ---- START OF FILE ScreenOperatorAccessibilityService.kt.txt --- |
318 | | - |
319 | | -```kotlin |
320 | 1 | package com.google.ai.sample |
321 | 2 |
|
322 | 3 | import android.accessibilityservice.AccessibilityService |
|
0 commit comments