hadadrjt commited on
Commit
913f308
·
1 Parent(s): d1f6044

ai: Modularize client logic into separate components.

Browse files

* Moved audio, image, and deep search handlers.
* Centralized input parsing and history management.
* Applied single-responsibility principle to streamline logic flow.

[i] Prepares the code for scalability as new features are added.

src/__init__.py CHANGED
File without changes
src/client/__init__.py CHANGED
File without changes
src/client/chat_handler.py CHANGED
@@ -10,14 +10,14 @@ from config import model # Import model configuration dictionary from config mo
10
  from src.core.server import jarvis # Import the async function to interact with AI backend
11
  from src.core.parameter import parameters # Import parameters (not used directly here but imported for completeness)
12
  from src.core.session import session # Import session dictionary to store conversation histories
13
- from src.tools.audio import AudioGeneration # Import AudioGeneration class to handle audio creation
14
- from src.tools.image import ImageGeneration # Import ImageGeneration class to handle image creation
15
- from src.tools.deep_search import SearchTools # Import SearchTools class for deep search functionality
 
 
16
  import gradio as gr # Import Gradio library for UI and request handling
17
 
18
- # Define an asynchronous function 'respond' to process user messages and generate AI responses
19
- # This function handles various types of user inputs including text, commands, and file uploads
20
- # It supports multiple AI models and generation modes with customizable parameters
21
  async def respond(
22
  message, # Incoming user message, can be a string or a dictionary containing text and files
23
  history: List[Any], # List containing conversation history as pairs of user and assistant messages
@@ -34,353 +34,96 @@ async def respond(
34
  request: gr.Request # Gradio request object to access session information such as session hash
35
  ):
36
  # Select the AI model based on the provided label, fallback to first model if label not found
37
- selected_model = model.get(model_label, list(model.values())[0])
38
-
39
- # Instantiate SearchTools class to enable deep search capabilities when requested by user
40
- search_tools = SearchTools()
41
-
42
  # Retrieve session ID from the Gradio request's session hash, generate new UUID if none exists
43
- session_id = request.session_hash or str(uuid.uuid4())
44
 
45
  # Initialize an empty conversation history list for this session if it does not already exist
46
- if session_id not in session:
47
- session[session_id] = []
48
 
49
  # Determine the mode string based on the 'thinking' flag, affects AI response generation behavior
50
- mode = "/think" if thinking else "/no_think"
51
-
52
- # Initialize variables for storing user input text and any attached files
53
- input = ""
54
- files = None
55
 
56
- # Check if the incoming message is a dictionary which may contain both text and file attachments
57
- if isinstance(message, dict):
58
- # Extract the text content from the message dictionary, default to empty string if missing
59
- input = message.get("text", "")
60
- # Extract the first file from the files list if present, otherwise set files to None
61
- files = message.get("files")[0] if message.get("files") else None
62
- else:
63
- # If the message is a simple string, assign it directly to input variable
64
- input = message
65
 
66
  # Strip leading and trailing whitespace from the input for clean processing
67
- stripped_input = input.strip()
68
  # Convert the stripped input to lowercase for case-insensitive command detection
69
- lowered_input = stripped_input.lower()
70
 
71
  # If the input is empty after stripping whitespace, yield an empty list and exit function early
72
- if not stripped_input:
73
- yield []
74
- return
75
 
76
  # If the input is exactly one of the command keywords without parameters, yield empty and exit early
77
- if lowered_input in ["/audio", "/image", "/dp"]:
78
- yield []
79
- return
80
 
81
  # Convert conversation history from tuples style to messages style format for AI model consumption
82
- # Transform list of [user_msg, assistant_msg] pairs into flat list of role-content dictionaries
83
- new_history = []
84
- for entry in history:
85
- # Ensure the entry is a list with exactly two elements: user message and assistant message
86
- if isinstance(entry, list) and len(entry) == 2:
87
- user_msg, assistant_msg = entry
88
- # Append the user message with role 'user' to the new history if message is not None
89
- if user_msg is not None:
90
- new_history.append({"role": "user", "content": user_msg})
91
- # Append the assistant message with role 'assistant' if it exists and is not None
92
- if assistant_msg is not None:
93
- new_history.append({"role": "assistant", "content": assistant_msg})
94
 
95
  # Update the global session dictionary with the newly formatted conversation history for this session
96
- session[session_id] = new_history
97
 
98
  # Handle audio generation command if enabled and input starts with '/audio' prefix
99
- if audio_gen and lowered_input.startswith("/audio"):
100
- # Extract the audio instruction text after the '/audio' command prefix and strip whitespace
101
- audio_instruction = input[6:].strip()
102
- # If no instruction text is provided after the command, yield empty and exit early
103
- if not audio_instruction:
104
- yield []
105
- return
106
- try:
107
- # Asynchronously create audio content based on the instruction using AudioGeneration class
108
- audio = await AudioGeneration.create_audio(audio_instruction)
109
- # Serialize the audio data and instruction into a JSON formatted string for processing
110
- audio_generation_content = json.dumps({
111
- "audio": audio,
112
- "audio_instruction": audio_instruction
113
- })
114
- # Construct the conversation history including the audio generation result and formatting instructions
115
- audio_generation_result = (
116
- new_history
117
- + [
118
- {
119
- "role": "system",
120
- "content": (
121
- "Audio generation result:\n\n" + audio_generation_content + "\n\n\n"
122
- "Show the audio using the following HTML audio tag format, where '{audio_link}' is the URL of the generated audio:\n\n"
123
- "<audio controls src='{audio_link}' style='width:100%; max-width:100%;'></audio>\n\n"
124
- "Please replace '{audio_link}' with the actual audio URL provided in the context.\n\n"
125
- "Then, describe the generated audio based on the above information.\n\n\n"
126
- "Use the same language as the previous user input or user request.\n"
127
- "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
128
- "If it is in English, explain in English. This also applies to other languages.\n\n\n"
129
- )
130
- }
131
- ]
132
- )
133
-
134
- # Use async generator to get descriptive text about the generated audio from AI
135
- async for audio_description in jarvis(
136
- session_id=session_id,
137
- model=selected_model,
138
- history=audio_generation_result,
139
- user_message=input,
140
- mode="/no_think", # Use non-reasoning mode to avoid extra processing overhead
141
- temperature=0.7, # Fixed temperature for consistent audio description generation
142
- top_k=20, # Limit token sampling to top 20 most probable tokens
143
- min_p=0, # Minimum probability threshold set to zero
144
- top_p=0.8, # Nucleus sampling threshold for quality control
145
- repetition_penalty=1 # No repetition penalty for this step
146
- ):
147
- # Yield the audio description wrapped in a tool role for proper UI display
148
- yield [{"role": "tool", "content": audio_description}]
149
- return
150
- except Exception:
151
- # If audio generation fails, let AI generate a contextual error message
152
- generation_failed = (
153
- new_history
154
- + [
155
- {
156
- "role": "system",
157
- "content": (
158
- "Audio generation failed for the user's request. The user tried to generate audio with the instruction: '"
159
- + audio_instruction + "'\n\n\n"
160
- "Please explain to the user that audio generation failed and suggest they wait 15 seconds before trying again.\n"
161
- "Be helpful and empathetic in your response.\n\n\n"
162
- "Use the same language as the previous user input or user request.\n"
163
- "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
164
- "If it is in English, explain in English. This also applies to other languages.\n\n\n"
165
- )
166
- }
167
- ]
168
- )
169
-
170
- # Use AI to generate a contextual error message
171
- async for error_response in jarvis(
172
- session_id=session_id,
173
- model=selected_model,
174
- history=generation_failed,
175
- user_message=input,
176
- mode="/no_think", # Use non-reasoning mode for error handling
177
- temperature=0.7, # Fixed temperature for more consistent error messages
178
- top_k=20, # Limit token sampling
179
- min_p=0, # Minimum probability threshold
180
- top_p=0.8, # Nucleus sampling threshold
181
- repetition_penalty=1 # No repetition penalty
182
- ):
183
- # Yield the AI-generated error response wrapped in tool role
184
- yield [{"role": "tool", "content": error_response}]
185
- return
186
 
187
  # Handle image generation command if enabled and input starts with '/image' prefix
188
- if image_gen and lowered_input.startswith("/image"):
189
- # Extract the image generation instruction after the '/image' command prefix and strip whitespace
190
- generate_image_instruction = input[6:].strip()
191
- # If no instruction text is provided after the command, yield empty and exit early
192
- if not generate_image_instruction:
193
- yield []
194
- return
195
- try:
196
- # Asynchronously create image content based on the instruction using ImageGeneration class
197
- image = await ImageGeneration.create_image(generate_image_instruction)
198
-
199
- # Serialize the image data and instruction into a JSON formatted string for processing
200
- image_generation_content = json.dumps({
201
- "image": image,
202
- "generate_image_instruction": generate_image_instruction
203
- })
204
-
205
- # Construct the conversation history including the image generation result and formatting instructions
206
- image_generation_result = (
207
- new_history
208
- + [
209
- {
210
- "role": "system",
211
- "content": (
212
- "Image generation result:\n\n" + image_generation_content + "\n\n\n"
213
- "Show the generated image using the following markdown syntax format, where '{image_link}' is the URL of the image:\n\n"
214
- "![Generated Image]({image_link})\n\n"
215
- "Please replace '{image_link}' with the actual image URL provided in the context.\n\n"
216
- "Then, describe the generated image based on the above information.\n\n\n"
217
- "Use the same language as the previous user input or user request.\n"
218
- "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
219
- "If it is in English, explain in English. This also applies to other languages.\n\n\n"
220
- )
221
- }
222
- ]
223
- )
224
-
225
- # Use async generator to get descriptive text about the generated image from AI
226
- async for image_description in jarvis(
227
- session_id=session_id,
228
- model=selected_model,
229
- history=image_generation_result,
230
- user_message=input,
231
- mode="/no_think", # Use non-reasoning mode to avoid extra processing overhead
232
- temperature=0.7, # Fixed temperature for consistent image description generation
233
- top_k=20, # Limit token sampling to top 20 most probable tokens
234
- min_p=0, # Minimum probability threshold set to zero
235
- top_p=0.8, # Nucleus sampling threshold for quality control
236
- repetition_penalty=1 # No repetition penalty for this step
237
- ):
238
- # Yield the image description wrapped in a tool role for proper UI display
239
- yield [{"role": "tool", "content": image_description}]
240
- return
241
- except Exception:
242
- # If image generation fails, let AI generate a contextual error message
243
- generation_failed = (
244
- new_history
245
- + [
246
- {
247
- "role": "system",
248
- "content": (
249
- "Image generation failed for the user's request. The user tried to generate an image with the instruction: '"
250
- + generate_image_instruction + "'\n\n\n"
251
- "Please explain to the user that image generation failed and suggest they wait 15 seconds before trying again.\n"
252
- "Be helpful and empathetic in your response.\n\n\n"
253
- "Use the same language as the previous user input or user request.\n"
254
- "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
255
- "If it is in English, explain in English. This also applies to other languages.\n\n\n"
256
- )
257
- }
258
- ]
259
- )
260
-
261
- # Use AI to generate a contextual error message
262
- async for error_response in jarvis(
263
- session_id=session_id,
264
- model=selected_model,
265
- history=generation_failed,
266
- user_message=input,
267
- mode="/no_think", # Use non-reasoning mode for error handling
268
- temperature=0.7, # Fixed temperature for more consistent error messages
269
- top_k=20, # Limit token sampling
270
- min_p=0, # Minimum probability threshold
271
- top_p=0.8, # Nucleus sampling threshold
272
- repetition_penalty=1 # No repetition penalty
273
- ):
274
- # Yield the AI-generated error response wrapped in tool role
275
- yield [{"role": "tool", "content": error_response}]
276
- return
277
 
278
  # Handle deep search command if enabled and input starts with '/dp' prefix
279
- if search_gen and lowered_input.startswith("/dp"):
280
- # Extract the search query after the '/dp' command prefix and strip whitespace
281
- search_query = input[3:].strip()
282
- # If no search query is provided after the command, yield empty and exit early
283
- if not search_query:
284
- yield []
285
- return
286
-
287
- try:
288
- # Perform an asynchronous deep search using SearchTools with the given query
289
- search_results = await search_tools.search(search_query)
290
-
291
- # Serialize the search query and results (limited to first 5000 characters) into JSON string
292
- search_content = json.dumps({
293
- "query": search_query,
294
- "search_results": search_results[:5000]
295
- })
296
-
297
- # Construct conversation history including deep search results and detailed instructions for summarization
298
- search_instructions = (
299
- new_history
300
- + [
301
- {
302
- "role": "system",
303
- "content": (
304
- "Deep search results for query: '" + search_query + "':\n\n\n" + search_content + "\n\n\n"
305
- "Please analyze these search results and provide a comprehensive summary of the information.\n"
306
- "Identify the most relevant information related to the query.\n"
307
- "Format your response in a clear, structured way with appropriate headings and bullet points if needed.\n"
308
- "If the search results don't provide sufficient information, acknowledge this limitation.\n"
309
- "Please provide links or URLs from each of your search results.\n\n\n"
310
- "Use the same language as the previous user input or user request.\n"
311
- "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
312
- "If it is in English, explain in English. This also applies to other languages.\n\n\n"
313
- )
314
- }
315
- ]
316
- )
317
-
318
- # Use async generator to process the deep search results and generate a summary response
319
- async for search_response in jarvis(
320
- session_id=session_id,
321
- model=selected_model,
322
- history=search_instructions,
323
- user_message=input,
324
- mode=mode, # Use the mode determined by the thinking flag
325
- temperature=temperature,
326
- top_k=top_k,
327
- min_p=min_p,
328
- top_p=top_p,
329
- repetition_penalty=repetition_penalty
330
- ):
331
- # Yield the search summary wrapped in a tool role for proper UI display
332
- yield [{"role": "tool", "content": search_response}]
333
- return
334
-
335
- except Exception as e:
336
- # If deep search fails, let AI generate a contextual error message
337
- generation_failed = (
338
- new_history
339
- + [
340
- {
341
- "role": "system",
342
- "content": (
343
- "Deep search failed for the user's query: '" + search_query + "'\n\n\n"
344
- "Please explain to the user that the search operation failed and suggest they try again later.\n"
345
- "Be helpful and empathetic in your response. You can also suggest alternative approaches or workarounds.\n\n\n"
346
- "Use the same language as the previous user input or user request.\n"
347
- "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
348
- "If it is in English, explain in English. This also applies to other languages.\n\n\n"
349
- )
350
- }
351
- ]
352
- )
353
-
354
- # Use AI to generate a contextual error message
355
- async for error_response in jarvis(
356
- session_id=session_id,
357
- model=selected_model,
358
- history=generation_failed,
359
- user_message=input,
360
- mode="/no_think", # Use non-reasoning mode for error handling
361
- temperature=0.7, # Fixed temperature for more consistent error messages
362
- top_k=20, # Limit token sampling
363
- min_p=0, # Minimum probability threshold
364
- top_p=0.8, # Nucleus sampling threshold
365
- repetition_penalty=1 # No repetition penalty
366
- ):
367
- # Yield the AI-generated error response wrapped in tool role
368
- yield [{"role": "tool", "content": error_response}]
369
- return
370
 
371
  # For all other inputs that do not match special commands, use the jarvis function to generate a normal response
372
  async for response in jarvis(
373
- session_id=session_id,
374
- model=selected_model,
375
  history=new_history, # Pass the conversation history
376
- user_message=input,
377
  mode=mode, # Use the mode determined by the thinking flag
378
  files=files, # Pass any attached files along with the message
379
- temperature=temperature,
380
- top_k=top_k,
381
- min_p=min_p,
382
- top_p=top_p,
383
- repetition_penalty=repetition_penalty
384
  ):
385
- # Yield each chunk of the response as it is generated by the AI model
386
- yield response
 
10
  from src.core.server import jarvis # Import the async function to interact with AI backend
11
  from src.core.parameter import parameters # Import parameters (not used directly here but imported for completeness)
12
  from src.core.session import session # Import session dictionary to store conversation histories
13
+ from src.utils.input import extract_input_and_files # Import utility to extract input and files from message
14
+ from src.utils.history import convert_history # Import utility to convert history format
15
+ from src.client.responses.audio import audio_integration # Import handler for audio generation
16
+ from src.client.responses.image import image_integration # Import handler for image generation
17
+ from src.client.responses.deep_search import deep_search_integration # Import handler for deep search
18
  import gradio as gr # Import Gradio library for UI and request handling
19
 
20
+ # Define the asynchronous respond function to process user messages and generate AI responses
 
 
21
  async def respond(
22
  message, # Incoming user message, can be a string or a dictionary containing text and files
23
  history: List[Any], # List containing conversation history as pairs of user and assistant messages
 
34
  request: gr.Request # Gradio request object to access session information such as session hash
35
  ):
36
  # Select the AI model based on the provided label, fallback to first model if label not found
37
+ selected_model = model.get(model_label, list(model.values())[0]) # Choose model based on label
38
+
 
 
 
39
  # Retrieve session ID from the Gradio request's session hash, generate new UUID if none exists
40
+ session_id = request.session_hash or str(uuid.uuid4()) # Get or create session ID
41
 
42
  # Initialize an empty conversation history list for this session if it does not already exist
43
+ if session_id not in session: # Check if session ID is not in session dictionary
44
+ session[session_id] = [] # Initialize empty history for new session
45
 
46
  # Determine the mode string based on the 'thinking' flag, affects AI response generation behavior
47
+ mode = "/think" if thinking else "/no_think" # Set mode based on thinking flag
 
 
 
 
48
 
49
+ # Extract input text and files from the message using utility function
50
+ input, files = extract_input_and_files(message) # Unpack input and files
 
 
 
 
 
 
 
51
 
52
  # Strip leading and trailing whitespace from the input for clean processing
53
+ stripped_input = input.strip() # Remove whitespace from input
54
  # Convert the stripped input to lowercase for case-insensitive command detection
55
+ lowered_input = stripped_input.lower() # Convert input to lowercase
56
 
57
  # If the input is empty after stripping whitespace, yield an empty list and exit function early
58
+ if not stripped_input: # Check if input is empty
59
+ yield [] # Yield empty list for empty input
60
+ return # Exit function
61
 
62
  # If the input is exactly one of the command keywords without parameters, yield empty and exit early
63
+ if lowered_input in ["/audio", "/image", "/dp"]: # Check for command keywords only
64
+ yield [] # Yield empty list for bare command
65
+ return # Exit function
66
 
67
  # Convert conversation history from tuples style to messages style format for AI model consumption
68
+ new_history = convert_history(history) # Convert history to message format
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  # Update the global session dictionary with the newly formatted conversation history for this session
71
+ session[session_id] = new_history # Update session with new history
72
 
73
  # Handle audio generation command if enabled and input starts with '/audio' prefix
74
+ if audio_gen and lowered_input.startswith("/audio"): # Check for audio command
75
+ async for audio_response in audio_integration(
76
+ input, # User input
77
+ new_history, # Conversation history
78
+ session_id, # Session ID
79
+ selected_model, # Selected model
80
+ jarvis # AI backend function
81
+ ):
82
+ yield audio_response # Yield audio response
83
+ return # Exit function after handling audio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  # Handle image generation command if enabled and input starts with '/image' prefix
86
+ if image_gen and lowered_input.startswith("/image"): # Check for image command
87
+ async for image_response in image_integration(
88
+ input, # User input
89
+ new_history, # Conversation history
90
+ session_id, # Session ID
91
+ selected_model, # Selected model
92
+ jarvis # AI backend function
93
+ ):
94
+ yield image_response # Yield image response
95
+ return # Exit function after handling image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  # Handle deep search command if enabled and input starts with '/dp' prefix
98
+ if search_gen and lowered_input.startswith("/dp"): # Check for deep search command
99
+ async for search_response in deep_search_integration(
100
+ input, # User input
101
+ new_history, # Conversation history
102
+ session_id, # Session ID
103
+ selected_model, # Selected model
104
+ jarvis, # AI backend function
105
+ mode, # Mode for AI response
106
+ temperature, # temperature parameter
107
+ top_k, # top_k parameter
108
+ min_p, # min_p parameter
109
+ top_p, # top_p parameter
110
+ repetition_penalty # repetition_penalty parameter
111
+ ):
112
+ yield search_response # Yield search response
113
+ return # Exit function after handling deep search
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  # For all other inputs that do not match special commands, use the jarvis function to generate a normal response
116
  async for response in jarvis(
117
+ session_id=session_id, # Session ID for conversation context
118
+ model=selected_model, # Selected model for generation
119
  history=new_history, # Pass the conversation history
120
+ user_message=input, # User input message
121
  mode=mode, # Use the mode determined by the thinking flag
122
  files=files, # Pass any attached files along with the message
123
+ temperature=temperature, # temperature parameter
124
+ top_k=top_k, # top_k parameter
125
+ min_p=min_p, # min_p parameter
126
+ top_p=top_p, # top_p parameter
127
+ repetition_penalty=repetition_penalty # repetition_penalty parameter
128
  ):
129
+ yield response # Yield each chunk of the response as it is generated by the AI model
 
src/client/responses/__init__.py ADDED
File without changes
src/client/responses/audio.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # SPDX-FileCopyrightText: Hadad <[email protected]>
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+
6
+ import json # Import JSON module for encoding and decoding JSON data
7
+ from src.tools.audio import AudioGeneration # Import AudioGeneration class to handle audio creation
8
+
9
+ # Asynchronous handler for audio generation command
10
+ async def audio_integration(
11
+ input, # User input containing the /audio command and instruction
12
+ new_history, # Conversation history in message format
13
+ session_id, # Session ID for conversation context
14
+ selected_model, # Selected AI model for generation
15
+ jarvis # AI backend function for generating responses
16
+ ):
17
+ # Extract the audio instruction text after the '/audio' command prefix and strip whitespace
18
+ audio_instruction = input[6:].strip() # Get instruction after /audio
19
+
20
+ # If no instruction text is provided after the command, yield empty and exit early
21
+ if not audio_instruction: # Check if instruction is empty
22
+ yield [] # Yield empty list for missing instruction
23
+ return # Exit function
24
+
25
+ try: # Try block for audio generation
26
+ # Asynchronously create audio content based on the instruction using AudioGeneration class
27
+ audio = await AudioGeneration.create_audio(audio_instruction) # Generate audio
28
+
29
+ # Serialize the audio data and instruction into a JSON formatted string for processing
30
+ audio_generation_content = json.dumps({
31
+ "audio": audio, # Audio content or URL
32
+ "audio_instruction": audio_instruction # Instruction for audio generation
33
+ })
34
+
35
+ # Construct the conversation history including the audio generation result and formatting instructions
36
+ audio_generation_result = (
37
+ new_history
38
+ + [
39
+ {
40
+ "role": "system",
41
+ "content": (
42
+ "Audio generation result:\n\n" + audio_generation_content + "\n\n\n"
43
+ "Show the audio using the following HTML audio tag format, where '{audio_link}' is the URL of the generated audio:\n\n"
44
+ "<audio controls src='{audio_link}' style='width:100%; max-width:100%;'></audio>\n\n"
45
+ "Please replace '{audio_link}' with the actual audio URL provided in the context.\n\n"
46
+ "Then, describe the generated audio based on the above information.\n\n\n"
47
+ "Use the same language as the previous user input or user request.\n"
48
+ "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
49
+ "If it is in English, explain in English. This also applies to other languages.\n\n\n"
50
+ )
51
+ }
52
+ ]
53
+ )
54
+
55
+ # Use async generator to get descriptive text about the generated audio from AI
56
+ async for audio_description in jarvis(
57
+ session_id=session_id, # Session ID
58
+ model=selected_model, # Selected model
59
+ history=audio_generation_result, # Updated history with audio result
60
+ user_message=input, # User input
61
+ mode="/no_think", # Use non-reasoning mode to avoid extra processing overhead
62
+ temperature=0.7, # Fixed temperature for consistent audio description generation
63
+ top_k=20, # Limit token sampling to top 20 most probable tokens
64
+ min_p=0, # Minimum probability threshold set to zero
65
+ top_p=0.8, # Nucleus sampling threshold for quality control
66
+ repetition_penalty=1 # No repetition penalty for this step
67
+ ):
68
+ yield [{"role": "tool", "content": audio_description}] # Yield audio description in tool role
69
+ return # Exit after handling audio
70
+
71
+ except Exception: # Exception handling for audio generation failure
72
+ # If audio generation fails, let AI generate a contextual error message
73
+ generation_failed = (
74
+ new_history
75
+ + [
76
+ {
77
+ "role": "system",
78
+ "content": (
79
+ "Audio generation failed for the user's request. The user tried to generate audio with the instruction: '"
80
+ + audio_instruction + "'\n\n\n"
81
+ "Please explain to the user that audio generation failed and suggest they wait 15 seconds before trying again.\n"
82
+ "Be helpful and empathetic in your response.\n\n\n"
83
+ "Use the same language as the previous user input or user request.\n"
84
+ "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
85
+ "If it is in English, explain in English. This also applies to other languages.\n\n\n"
86
+ )
87
+ }
88
+ ]
89
+ )
90
+
91
+ # Use AI to generate a contextual error message
92
+ async for error_response in jarvis(
93
+ session_id=session_id, # Session ID
94
+ model=selected_model, # Selected model
95
+ history=generation_failed, # History with error context
96
+ user_message=input, # User input
97
+ mode="/no_think", # Use non-reasoning mode for error handling
98
+ temperature=0.7, # Fixed temperature for more consistent error messages
99
+ top_k=20, # Limit token sampling
100
+ min_p=0, # Minimum probability threshold
101
+ top_p=0.8, # Nucleus sampling threshold
102
+ repetition_penalty=1 # No repetition penalty
103
+ ):
104
+ yield [{"role": "tool", "content": error_response}] # Yield error response in tool role
105
+ return # Exit after error handling
src/client/responses/deep_search.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # SPDX-FileCopyrightText: Hadad <[email protected]>
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+
6
+ import json # Import JSON module for encoding and decoding JSON data
7
+ from src.tools.deep_search import SearchTools # Import SearchTools class for deep search functionality
8
+
9
+ # Asynchronous handler for deep search command
10
+ async def deep_search_integration(
11
+ input, # User input containing the /dp command and query
12
+ new_history, # Conversation history in message format
13
+ session_id, # Session ID for conversation context
14
+ selected_model, # Selected AI model for generation
15
+ jarvis, # AI backend function for generating responses
16
+ mode, # Mode for AI response generation
17
+ temperature, # Temperature parameter for AI
18
+ top_k, # Top-k parameter for AI
19
+ min_p, # Min-p parameter for AI
20
+ top_p, # Top-p parameter for AI
21
+ repetition_penalty # Repetition penalty for AI
22
+ ):
23
+ # Instantiate SearchTools class to enable deep search capabilities when requested by user
24
+ search_tools = SearchTools() # Create SearchTools instance
25
+
26
+ # Extract the search query after the '/dp' command prefix and strip whitespace
27
+ search_query = input[3:].strip() # Get search query after /dp
28
+
29
+ # If no search query is provided after the command, yield empty and exit early
30
+ if not search_query: # Check if search query is empty
31
+ yield [] # Yield empty list for missing search query
32
+ return # Exit function
33
+
34
+ try: # Try block for deep search
35
+ # Perform an asynchronous deep search using SearchTools with the given query
36
+ search_results = await search_tools.search(search_query) # Perform deep search
37
+
38
+ # Serialize the search query and results (limited to first 5000 characters) into JSON string
39
+ search_content = json.dumps({
40
+ "query": search_query, # Search query
41
+ "search_results": search_results[:5000] # Search results limited to 5000 characters
42
+ })
43
+
44
+ # Construct conversation history including deep search results and detailed instructions for summarization
45
+ search_instructions = (
46
+ new_history
47
+ + [
48
+ {
49
+ "role": "system",
50
+ "content": (
51
+ "Deep search results for query: '" + search_query + "':\n\n\n" + search_content + "\n\n\n"
52
+ "Please analyze these search results and provide a comprehensive summary of the information.\n"
53
+ "Identify the most relevant information related to the query.\n"
54
+ "Format your response in a clear, structured way with appropriate headings and bullet points if needed.\n"
55
+ "If the search results don't provide sufficient information, acknowledge this limitation.\n"
56
+ "Please provide links or URLs from each of your search results.\n\n\n"
57
+ "Use the same language as the previous user input or user request.\n"
58
+ "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
59
+ "If it is in English, explain in English. This also applies to other languages.\n\n\n"
60
+ )
61
+ }
62
+ ]
63
+ )
64
+
65
+ # Use async generator to process the deep search results and generate a summary response
66
+ async for search_response in jarvis(
67
+ session_id=session_id, # Session ID
68
+ model=selected_model, # Selected model
69
+ history=search_instructions, # Updated history with search results
70
+ user_message=input, # User input
71
+ mode=mode, # Mode for AI response
72
+ temperature=temperature, # temperature parameter
73
+ top_k=top_k, # top_k parameter
74
+ min_p=min_p, # min_p parameter
75
+ top_p=top_p, # top_p parameter
76
+ repetition_penalty=repetition_penalty # repetition_penalty parameter
77
+ ):
78
+ yield [{"role": "tool", "content": search_response}] # Yield search summary in tool role
79
+ return # Exit after handling deep search
80
+
81
+ except Exception as e: # Exception handling for deep search failure
82
+ # If deep search fails, let AI generate a contextual error message
83
+ generation_failed = (
84
+ new_history
85
+ + [
86
+ {
87
+ "role": "system",
88
+ "content": (
89
+ "Deep search failed for the user's query: '" + search_query + "'\n\n\n"
90
+ "Please explain to the user that the search operation failed and suggest they try again later.\n"
91
+ "Be helpful and empathetic in your response. You can also suggest alternative approaches or workarounds.\n\n\n"
92
+ "Use the same language as the previous user input or user request.\n"
93
+ "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
94
+ "If it is in English, explain in English. This also applies to other languages.\n\n\n"
95
+ )
96
+ }
97
+ ]
98
+ )
99
+
100
+ # Use AI to generate a contextual error message
101
+ async for error_response in jarvis(
102
+ session_id=session_id, # Session ID
103
+ model=selected_model, # Selected model
104
+ history=generation_failed, # History with error context
105
+ user_message=input, # User input
106
+ mode="/no_think", # Use non-reasoning mode for error handling
107
+ temperature=0.7, # Fixed temperature for more consistent error messages
108
+ top_k=20, # Limit token sampling
109
+ min_p=0, # Minimum probability threshold
110
+ top_p=0.8, # Nucleus sampling threshold
111
+ repetition_penalty=1 # No repetition penalty
112
+ ):
113
+ yield [{"role": "tool", "content": error_response}] # Yield error response in tool role
114
+ return # Exit after error handling
src/client/responses/image.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # SPDX-FileCopyrightText: Hadad <[email protected]>
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+
6
+ import json # Import JSON module for encoding and decoding JSON data
7
+ from src.tools.image import ImageGeneration # Import ImageGeneration class to handle image creation
8
+
9
+ # Asynchronous handler for image generation command
10
+ async def image_integration(
11
+ input, # User input containing the /image command and instruction
12
+ new_history, # Conversation history in message format
13
+ session_id, # Session ID for conversation context
14
+ selected_model, # Selected AI model for generation
15
+ jarvis # AI backend function for generating responses
16
+ ):
17
+ # Extract the image generation instruction after the '/image' command prefix and strip whitespace
18
+ generate_image_instruction = input[6:].strip() # Get instruction after /image
19
+
20
+ # If no instruction text is provided after the command, yield empty and exit early
21
+ if not generate_image_instruction: # Check if instruction is empty
22
+ yield [] # Yield empty list for missing instruction
23
+ return # Exit function
24
+
25
+ try: # Try block for image generation
26
+ # Asynchronously create image content based on the instruction using ImageGeneration class
27
+ image = await ImageGeneration.create_image(generate_image_instruction) # Generate image
28
+
29
+ # Serialize the image data and instruction into a JSON formatted string for processing
30
+ image_generation_content = json.dumps({
31
+ "image": image, # Image content or URL
32
+ "generate_image_instruction": generate_image_instruction # Instruction for image generation
33
+ })
34
+
35
+ # Construct the conversation history including the image generation result and formatting instructions
36
+ image_generation_result = (
37
+ new_history
38
+ + [
39
+ {
40
+ "role": "system",
41
+ "content": (
42
+ "Image generation result:\n\n" + image_generation_content + "\n\n\n"
43
+ "Show the generated image using the following markdown syntax format, where '{image_link}' is the URL of the image:\n\n"
44
+ "![Generated Image]({image_link})\n\n"
45
+ "Please replace '{image_link}' with the actual image URL provided in the context.\n\n"
46
+ "Then, describe the generated image based on the above information.\n\n\n"
47
+ "Use the same language as the previous user input or user request.\n"
48
+ "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
49
+ "If it is in English, explain in English. This also applies to other languages.\n\n\n"
50
+ )
51
+ }
52
+ ]
53
+ )
54
+
55
+ # Use async generator to get descriptive text about the generated image from AI
56
+ async for image_description in jarvis(
57
+ session_id=session_id, # Session ID
58
+ model=selected_model, # Selected model
59
+ history=image_generation_result, # Updated history with image result
60
+ user_message=input, # User input
61
+ mode="/no_think", # Use non-reasoning mode to avoid extra processing overhead
62
+ temperature=0.7, # Fixed temperature for consistent image description generation
63
+ top_k=20, # Limit token sampling to top 20 most probable tokens
64
+ min_p=0, # Minimum probability threshold set to zero
65
+ top_p=0.8, # Nucleus sampling threshold for quality control
66
+ repetition_penalty=1 # No repetition penalty for this step
67
+ ):
68
+ yield [{"role": "tool", "content": image_description}] # Yield image description in tool role
69
+ return # Exit after handling image
70
+
71
+ except Exception: # Exception handling for image generation failure
72
+ # If image generation fails, let AI generate a contextual error message
73
+ generation_failed = (
74
+ new_history
75
+ + [
76
+ {
77
+ "role": "system",
78
+ "content": (
79
+ "Image generation failed for the user's request. The user tried to generate an image with the instruction: '"
80
+ + generate_image_instruction + "'\n\n\n"
81
+ "Please explain to the user that image generation failed and suggest they wait 15 seconds before trying again.\n"
82
+ "Be helpful and empathetic in your response.\n\n\n"
83
+ "Use the same language as the previous user input or user request.\n"
84
+ "For example, if the previous user input or user request is in Indonesian, explain in Indonesian.\n"
85
+ "If it is in English, explain in English. This also applies to other languages.\n\n\n"
86
+ )
87
+ }
88
+ ]
89
+ )
90
+
91
+ # Use AI to generate a contextual error message
92
+ async for error_response in jarvis(
93
+ session_id=session_id, # Session ID
94
+ model=selected_model, # Selected model
95
+ history=generation_failed, # History with error context
96
+ user_message=input, # User input
97
+ mode="/no_think", # Use non-reasoning mode for error handling
98
+ temperature=0.7, # Fixed temperature for more consistent error messages
99
+ top_k=20, # Limit token sampling
100
+ min_p=0, # Minimum probability threshold
101
+ top_p=0.8, # Nucleus sampling threshold
102
+ repetition_penalty=1 # No repetition penalty
103
+ ):
104
+ yield [{"role": "tool", "content": error_response}] # Yield error response in tool role
105
+ return # Exit after error handling
src/core/__init__.py CHANGED
File without changes
src/tools/__init__.py CHANGED
File without changes
src/ui/__init__.py CHANGED
File without changes
src/utils/__init__.py CHANGED
File without changes
src/utils/history.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # SPDX-FileCopyrightText: Hadad <[email protected]>
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+
6
+ # Utility function to convert conversation history format for AI model consumption
7
+ def convert_history(history): # Convert history to message format
8
+ """
9
+ Converts a list of [user_msg, assistant_msg] pairs into a flat list of role-content dictionaries.
10
+ This format is required for AI model input.
11
+ """
12
+ new_history = [] # Initialize new history list
13
+
14
+ for entry in history: # Iterate over each entry in history
15
+ # Ensure the entry is a list with exactly two elements: user message and assistant message
16
+ if isinstance(entry, list) and len(entry) == 2: # Check entry structure
17
+ user_msg, assistant_msg = entry # Unpack user and assistant messages
18
+
19
+ if user_msg is not None: # Check if user message is not None
20
+ new_history.append({"role": "user", "content": user_msg}) # Add user message to new history
21
+
22
+ if assistant_msg is not None: # Check if assistant message is not None
23
+ new_history.append({"role": "assistant", "content": assistant_msg}) # Add assistant message to new history
24
+
25
+ return new_history # Return the converted history list
src/utils/input.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # SPDX-FileCopyrightText: Hadad <[email protected]>
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+
6
+ # Utility function to extract input text and files from a message object
7
+ def extract_input_and_files(message): # Extract input and files from message
8
+ """
9
+ Extracts the input text and any attached files from the message object.
10
+ Returns a tuple (input, files).
11
+ """
12
+ input = "" # Initialize input as empty string
13
+ files = None # Initialize files as None
14
+
15
+ # Check if the message is a dictionary which may contain both text and file attachments
16
+ if isinstance(message, dict): # If message is a dictionary
17
+ input = message.get("text", "") # Get text from message dictionary
18
+ files = message.get("files")[0] if message.get("files") else None # Get first file if present
19
+ else: # If message is a simple string
20
+ input = message # Assign message directly to input
21
+
22
+ return input, files # Return extracted input and files