-
Notifications
You must be signed in to change notification settings - Fork 2
/
anylanguage.py
368 lines (336 loc) · 18.6 KB
/
anylanguage.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
import requests
import json
class AnyLanguage:
"""
V1.1.8
By Peng.Bo
SAHO Inc.
A node for translating multi-language prompts into English using OpenAI's API and encoding them with a CLIP model.
Class methods
-------------
INPUT_TYPES (dict):
Specify input parameters for the node.
Attributes
----------
RETURN_TYPES (`tuple`):
The type of each element in the output tuple.
FUNCTION (`str`):
The name of the entry-point method. In this case, it will run AnyLanguage().encode().
CATEGORY (`str`):
The category the node should appear in the UI.
DESCRIPTION (`str`):
A description of the node's functionality.
"""
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"api_url": ("STRING", {
"multiline": False,
"default": "https://api.openai.com/v1/chat/completions",
"tooltip": "Enter the API URL here",
"lazy": False
}),
"api_key": ("STRING", {
"multiline": False,
"default": "",
"placeholder": "Enter your OpenAI API Key here",
"lazy": False
}),
"model": ([ "gpt-4o-mini","gpt-3.5-turbo", "gpt-4o-2024-08-06", "gpt-4o","gpt-4-turbo", "gpt-4","o1-mini", "o1-preview"],
{
"default": "gpt-4o-mini"
}),
"multilingual_prompt": ("STRING", {
"multiline": True,
"dynamicPrompts": True,
"tooltip": "The multilingual prompt to be translated and encoded (optional)."
}),
"english_prompt": ("STRING", {
"multiline": True,
"dynamicPrompts": True,
"tooltip": "The English prompt to append to the translated prompt (optional)."
}),
"clip": ("CLIP", {"tooltip": "The CLIP model used for encoding the translated text."})
},
"optional": {
"additional_output": ("STRING", {
"multiline": False,
"default": "",
"tooltip": "Optional additional output for error messages."
})
}
}
RETURN_TYPES = ("CONDITIONING", "STRING")
OUTPUT_NAMES = ("conditioning", "error_message")
OUTPUT_TOOLTIPS = (
"A conditioning containing the embedded translated text used to guide the diffusion model.",
"Error message detailing any issues encountered during API call."
)
FUNCTION = "encode"
CATEGORY = "conditioning"
DESCRIPTION = "Translates a text prompt using the OpenAI API and encodes it with a CLIP model into an embedding that can be used to guide the diffusion model towards generating specific images."
def encode(self, api_key, api_url, model, multilingual_prompt, english_prompt, clip, additional_output=None):
"""
Translates the given multilingual prompt to English, then encodes it using the CLIP model.
Parameters:
api_key (str): The OpenAI API key provided by the user.
api_url (str): The API URL provided by the user.
model (str): The model selected by the user from the dropdown menu.
multilingual_prompt (str): The multilingual prompt to be translated and encoded (optional).
english_prompt (str): The English prompt to append to the translated prompt (optional).
clip: The CLIP model used to encode the text.
additional_output (str): Optional additional output.
Returns:
tuple: A tuple containing the conditioning embedding and an error message (if any).
"""
# ANSI color codes
RED = "\033[91m"
GREEN = "\033[92m"
RESET = "\033[0m"
# Define default prompts
default_prompt_exception = "a cute 3D style red kitten"
default_prompt_refusal = "a cute 3D style yellow kitten"
# Define JSON Schema for Structured Outputs
structured_json_schema = {
"type": "object",
"properties": {
"translated_text": {
"type": "string"
}
},
"required": ["translated_text"],
"additionalProperties": False
}
error_message = ""
try:
translated_text = ""
# Check if the model name contains 'o1' (case-insensitive)
is_o1_model = "o1" in model.lower()
# Check if the model name contains '4o' (case-insensitive)
supports_structured_output = model.lower() in ["gpt-4o-mini", "gpt-4o-2024-08-06"]
# Construct messages based on model capabilities
if multilingual_prompt.strip():
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
if is_o1_model:
# Models with 'o1' do not support 'system' role
# Combine system instructions into the user message
user_content = (
"You are a translation expert. Your task is to accurately and fluently translate the following text into English, "
"ensuring the translation is optimized for use as a prompt in AI-generated image creation. "
"The translation should be natural, precise, and tailored for generating visual content. "
"Do not add any additional comments or notes. "
+ multilingual_prompt
)
messages = [
{
"role": "user",
"content": user_content
}
]
else:
# Models that support 'system' role
messages = [
{
"role": "system",
"content": "You are a translation expert. Your task is to accurately and fluently translate the following text into English, ensuring the translation is optimized for use as a prompt in AI-generated image creation. The translation should be natural, precise, and tailored for generating visual content. Do not add any additional comments or notes."
},
{
"role": "user",
"content": multilingual_prompt
}
]
payload = {
"model": model,
"messages": messages
}
print(f"OpenAI API Request model: {model}")
if supports_structured_output:
payload["response_format"] = {
"type": "json_schema",
"json_schema": {
"name": "translation_response",
"strict": True,
"schema": structured_json_schema
}
}
response = requests.post(api_url, headers=headers, data=json.dumps(payload))
print(f"OpenAI API Response: {response.status_code}")
# Attempt to parse the response regardless of status code
try:
response_data = response.json()
except json.JSONDecodeError:
error_message = "Failed to parse JSON response from OpenAI API."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_refusal
return ([[None, None]], error_message)
if response.status_code == 200:
if supports_structured_output:
# Handle Structured Outputs
if "error" in response_data:
# API returned an error
error_message = response_data["error"].get("message", "Unknown error. Using default refusal prompt.")
print(f"{RED}OpenAI API returned an error: {error_message}{RESET}")
print(f"{RED}Using default refusal prompt.{RESET}")
translated_text = default_prompt_refusal
elif "choices" in response_data and len(response_data["choices"]) > 0:
choice = response_data["choices"][0]
if "message" in choice and "content" in choice["message"]:
# Extract translated_text from the structured response
try:
translation_response = choice["message"]["content"]
# Parse the structured JSON response
translation_json = json.loads(translation_response)
translated_text = translation_json.get("translated_text", "").strip()
if not translated_text:
error_message = "Translation result is empty. Using default exception prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_exception
except json.JSONDecodeError:
error_message = "Failed to decode structured JSON response. Using default refusal prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_refusal
else:
# If the expected fields are missing, use refusal default prompt
error_message = "Unexpected response format. Using default refusal prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_refusal
else:
# If the response format is unexpected, use refusal default prompt
error_message = "Unexpected response structure. Using default refusal prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_refusal
else:
# Handle Traditional Responses
if "error" in response_data:
# API returned an error
error_message = response_data["error"].get("message", "Unknown error. Using default refusal prompt.")
print(f"{RED}OpenAI API returned an error: {error_message}{RESET}")
print(f"{RED}Using default refusal prompt.{RESET}")
translated_text = default_prompt_refusal
elif "choices" in response_data and len(response_data["choices"]) > 0:
choice = response_data["choices"][0]
if "message" in choice and "content" in choice["message"]:
translated_text = choice["message"]["content"].strip()
if not translated_text:
# If translation result is empty, use exception default prompt
error_message = "Translation result is empty. Using default exception prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_exception
else:
# If the expected fields are missing, use refusal default prompt
error_message = "Unexpected response format. Using default refusal prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_refusal
else:
# If the response format is unexpected, use refusal default prompt
error_message = "Unexpected response structure. Using default refusal prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_refusal
else:
# Non-200 status codes
if "error" in response_data and "message" in response_data["error"]:
error_message = response_data["error"]["message"]
else:
error_message = f"Unexpected error with status code {response.status_code}."
print(f"{RED}OpenAI API returned an error: {error_message}{RESET}")
print(f"{RED}Using default exception prompt.{RESET}")
translated_text = default_prompt_exception if "exception" in error_message.lower() else default_prompt_refusal
# If english_prompt has content, append it to the translated prompt
if english_prompt.strip():
if translated_text:
translated_text += " " + english_prompt
else:
translated_text = english_prompt
# If neither multilingual_prompt nor english_prompt has content, use default prompt for exception
if not translated_text:
translated_text = default_prompt_exception
# Print the final prompt being used in green
print(f"{GREEN}Final Prompt: {translated_text}{RESET}")
try:
# Encode the prompt using the CLIP model
tokens = clip.tokenize(translated_text)
output = clip.encode_from_tokens(tokens, return_pooled=True, return_dict=True)
cond = output.pop("cond")
return ([[cond, output]], error_message)
except Exception as e:
# Handle encoding errors
error_message += f" Encoding failed: {e}. Using default exception prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_exception
print(f"{RED}Final Prompt after encoding failure: {translated_text}{RESET}")
tokens = clip.tokenize(translated_text)
try:
output = clip.encode_from_tokens(tokens, return_pooled=True, return_dict=True)
cond = output.pop("cond")
return ([[cond, output]], error_message)
except Exception as encode_error:
# Handle encoding errors
error_message += f" Encoding failed: {encode_error}. Using default exception prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_exception
print(f"{RED}Final Prompt after encoding failure: {translated_text}{RESET}")
return ([[None, None]], error_message)
except requests.exceptions.RequestException as e:
# Handle network-related errors
if e.response is not None:
try:
error_data = e.response.json()
error_message = error_data["error"].get("message", str(e))
except ValueError:
error_message = str(e)
else:
error_message = str(e)
print(f"{RED}Request failed: {error_message}. Using default exception prompt.{RESET}")
# Use exception default prompt in case of request exception
translated_text = default_prompt_exception
print(f"{GREEN}Final Prompt: {translated_text}{RESET}")
try:
tokens = clip.tokenize(translated_text)
output = clip.encode_from_tokens(tokens, return_pooled=True, return_dict=True)
cond = output.pop("cond")
return ([[cond, output]], error_message)
except Exception as encode_error:
# Handle encoding errors
error_message += f" Encoding failed: {encode_error}. Using default exception prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_exception
print(f"{RED}Final Prompt after encoding failure: {translated_text}{RESET}")
return ([[None, None]], error_message)
except Exception as e:
# Handle any other unexpected errors
error_message = f"An unexpected error occurred: {e}. Using default exception prompt."
print(f"{RED}{error_message}{RESET}")
# Use exception default prompt in case of any other exception
translated_text = default_prompt_exception
print(f"{GREEN}Final Prompt: {translated_text}{RESET}")
try:
tokens = clip.tokenize(translated_text)
output = clip.encode_from_tokens(tokens, return_pooled=True, return_dict=True)
cond = output.pop("cond")
return ([[cond, output]], error_message)
except Exception as encode_error:
# Handle encoding errors
error_message += f" Encoding failed: {encode_error}. Using default exception prompt."
print(f"{RED}{error_message}{RESET}")
translated_text = default_prompt_exception
print(f"{RED}Final Prompt after encoding failure: {translated_text}{RESET}")
return ([[None, None]], error_message)
# Add custom API routes using router
from aiohttp import web
from server import PromptServer
@PromptServer.instance.routes.get("/translate")
async def get_translation(request):
return web.json_response("Translation service is active.")
# Export all custom nodes and their display names
NODE_CLASS_MAPPINGS = {
"AnyLanguage": AnyLanguage
}
NODE_DISPLAY_NAME_MAPPINGS = {
"AnyLanguage": "Multilanguage Prompt Translation"
}