o
    il                     @   s  d dl Z d dlZd dlmZ d dlmZ d dlZd dlZd dl	m
Z
 d dlmZ d dlZd dlmZmZmZ d dlmZmZmZmZmZmZmZmZmZmZmZ d dlmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z& d	Z'd
Z(G dd de)eZ*d&de+dej,fddZ-G dd dej.Z/G dd dej.Z0dede1dB fddZ2dede1dB fddZ3G dd dej.Z4G dd dej.Z5G dd dej.Z6G d d! d!ej.Z7G d"d# d#eZ8de8fd$d%Z9dS )'    N)Enum)BytesIO)Image)override)IOComfyExtensionInput)InputFileContentInputImageContentInputMessageInputTextContentModelResponsePropertiesOpenAICreateResponseOpenAIImageEditRequestOpenAIImageGenerationRequestOpenAIImageGenerationResponseOpenAIResponseOutputContent)ApiEndpointdownload_url_to_bytesiodownscale_image_tensorpoll_opsync_optensor_to_base64_stringtext_filepath_to_data_urivalidate_stringz/proxy/openai/v1/responsesz<starting_point_id:(.*)>c                   @   s4   e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdS )SupportedOpenAIModelzo4-minio1o3zo1-prozgpt-4.1zgpt-4.1-minizgpt-4.1-nanozgpt-5z
gpt-5-miniz
gpt-5-nanoN)__name__
__module____qualname__o4_minir   r   o1_progpt_4_1gpt_4_1_minigpt_4_1_nanogpt_5
gpt_5_mini
gpt_5_nano r*   r*   :/mnt/c/Users/fbmor/ComfyUI/comfy_api_nodes/nodes_openai.pyr   )   s    r   timeoutreturnc                    s   | j }|rt|dkrtdg }|D ]?}|jr"tt|j}n|jr4t }t|j||dI dH  ntdt	
|d}t|tjd }|t| qtj|ddS )	aK  Validates and casts a response to a torch.Tensor.

    Args:
        response: The response to validate and cast.
        timeout: Request timeout in seconds. Defaults to None (no timeout).

    Returns:
        A torch.Tensor representing the image (1, H, W, C).

    Raises:
        ValueError: If the response is not valid.
    r   z$No images returned from API endpoint)r,   Nu>   Invalid image payload – neither URL nor base64 data present.RGBAg     o@)dim)datalen
ValueErrorb64_jsonr   base64	b64decodeurlr   r   openconvertnpasarrayastypefloat32appendtorch
from_numpystack)responser,   r0   image_tensorsimg_dataimg_iopil_imgarrr*   r*   r+   validate_and_cast_response6   s    rG   c                   @   s8   e Zd Zedd Ze					d
dejfdd	ZdS )OpenAIDalle2c                 C   s   t jddddt jjddddd	t jjd
ddddt jjdddd	t jjddg ddddt jjddddddt jjddt jjddddt j	jddddgt j
 gt jjt jjt jjgdt jt jddgddd d!	S )"NrH   u   OpenAI DALL·E 2api node/image/OpenAIu?   Generates images synchronously via OpenAI's DALL·E 2 endpoint.prompt T   Text prompt for DALL·Edefault	multilinetooltipseedr      not implemented yet in backendrN   minmaxstepdisplay_modecontrol_after_generaterP   optionalsize	1024x1024)256x256512x512r]   
Image sizerN   optionsrP   r[   n   How many images to generaterN   rV   rW   rX   rP   rY   r[   image+Optional reference image for image editing.rP   r[   mask;Optional mask for inpainting (white areas will be replaced)widgetsa  
                (
                  $size := widgets.size;
                  $nRaw := widgets.n;
                  $n := ($nRaw != null and $nRaw != 0) ? $nRaw : 1;

                  $base :=
                    $contains($size, "256x256") ? 0.016 :
                    $contains($size, "512x512") ? 0.018 :
                    0.02;

                  {"type":"usd","usd": $round($base * $n, 3)}
                )
                
depends_onexpr	node_iddisplay_namecategorydescriptioninputsoutputshiddenis_api_nodeprice_badger   SchemaStringr   IntNumberDisplaynumberCombor   MaskOutputHiddenauth_token_comfy_orgapi_key_comfy_org	unique_id
PriceBadgePriceBadgeDependsclsr*   r*   r+   define_schema^   s|   
/zOpenAIDalle2.define_schemar   NrS   r]   r-   c                    s  t |dd d}d}d}	t}
d }|d ur|d urd}d}	t}
|  }|j\}}}tj||dd	d
}||d d d d d |f< |jdd  |jdd krStdd|   |d d d d df< t	|
d }| d tj}t|}t }|j|dd |d |}d|_n|d us|d urtdt| t|ddt|
|||||d|rdd|dfind |	dI d H }tt|I d H S )NFstrip_whitespacezdall-e-2 /proxy/openai/images/generationszapplication/json/proxy/openai/images/editsmultipart/form-data   cpudevicerS   $Mask and Image must be the same size   r      PNGformatz	image.pngz3Dall-E 2 image editing requires an image AND a maskPOSTpathmethod)modelrJ   rc   r\   rQ   rg   	image/png)response_modelr0   filescontent_type)r   r   r   squeezer   shaper>   ones	Exceptionr   	unsqueezenumpyr;   r9   uint8r   	fromarrayr   saveseeknamer   r   r   r   
NodeOutputrG   )r   rJ   rQ   rg   rj   rc   r\   r   r   r   request_class
img_binaryinput_tensorheightwidthchannelsrgba_tensorimage_npimgimg_byte_arrrA   r*   r*   r+   execute   s^   
"



zOpenAIDalle2.execute)r   NNrS   r]   r   r    r!   classmethodr   r   r   r   r*   r*   r*   r+   rH   \   s    
PrH   c                   @   s6   e Zd Zedd Ze				ddejfdd	Zd
S )OpenAIDalle3c                 C   s   t jddddt jjddddd	t jjd
ddddt jjdddd	t jjddddgdddt jjddddgdddt jjddg ddddgt j	 gt j
jt j
jt j
jgdt jt jddgdddd 	S )!Nr   u   OpenAI DALL·E 3rI   u?   Generates images synchronously via OpenAI's DALL·E 3 endpoint.rJ   rK   TrL   rM   rQ   r   rR   rS   rT   rU   qualitystandardhdzImage qualityra   stylenaturalvividzVivid causes the model to lean towards generating hyper-real and dramatic images. Natural causes the model to produce more natural, less hyper-real looking images.r\   r]   )r]   	1024x1792	1792x1024r`   rl   a  
                (
                  $size := widgets.size;
                  $q := widgets.quality;
                  $hd := $contains($q, "hd");

                  $price :=
                    $contains($size, "1024x1024")
                      ? ($hd ? 0.08 : 0.04)
                      : (($contains($size, "1792x1024") or $contains($size, "1024x1792"))
                          ? ($hd ? 0.12 : 0.08)
                          : 0.04);

                  {"type":"usd","usd": $price}
                )
                rn   rq   )r   r|   r}   r   r~   r   r   r   r   r   r   r   r   r   r   r   r   r*   r*   r+   r      sp   )zOpenAIDalle3.define_schemar   r   r   r]   r-   c                    sT   t |dd d}t| tdddtt||||||ddI d H }tt|I d H S )	NFr   zdall-e-3r   r   r   )r   rJ   r   r\   r   rQ   r   r0   )r   r   r   r   r   r   r   rG   )r   rJ   rQ   r   r   r\   r   rA   r*   r*   r+   r   A  s"   	
zOpenAIDalle3.executeN)r   r   r   r]   r   r*   r*   r*   r+   r      s    
Lr   rA   c                 C      | j jd | j jd  d S )Ng      $@g      D@    .Ausageinput_tokensoutput_tokensrA   r*   r*   r+   calculate_tokens_price_image_1_  s   r   c                 C   r   )Ng       @g      @@r   r   r   r*   r*   r+    calculate_tokens_price_image_1_5d  s   r   c                   @   sn   e Zd Zedd Ze									dd
ededededejdB dejdB dededede	j
fddZdS )OpenAIGPTImage1c                 C   s  t jddddt jjddddd	t jjd
ddddt jjdddd	t jjddg ddddt jjddg ddddt jjddg ddddt jjddddddt jjddt jjd d!dd"t j	jd#d$dd"t jjd%d&d'gd'dd(g	t j
 gt jjt jjt jjgdt jt jddgd)d*d+d,	S )-Nr   zOpenAI GPT Image 1.5rI   z?Generates images synchronously via OpenAI's GPT Image endpoint.rJ   rK   TzText prompt for GPT ImagerM   rQ   r   rR   rS   rT   rU   r   low)r   mediumhighz0Image quality, affects cost and generation time.ra   
backgroundauto)r   opaquetransparentz'Return image with or without backgroundr\   )r   r]   	1024x1536	1536x1024r`   rc   rd   re   rf   rg   rh   ri   rj   rk   r   gpt-image-1gpt-image-1.5)rb   rN   r[   rl   a  
                (
                  $ranges := {
                    "low":    [0.011, 0.02],
                    "medium": [0.046, 0.07],
                    "high":   [0.167, 0.3]
                  };
                  $range := $lookup($ranges, widgets.quality);
                  $n := widgets.n;
                  ($n = 1)
                    ? {"type":"range_usd","min_usd": $range[0], "max_usd": $range[1]}
                    : {
                        "type":"range_usd",
                        "min_usd": $range[0],
                        "max_usd": $range[1],
                        "format": { "suffix": " x " & $string($n) & "/Run" }
                      }
                )
                rn   rq   r{   r   r*   r*   r+   r   j  s   
CzOpenAIGPTImage1.define_schemar   r   r   NrS   r]   r   rJ   rQ   r   r   rg   rj   rc   r\   r   r-   c
                    s~  t |dd |d ur|d u rtd|	dkrt}
n|	dkr!t}
ntd|	 |d urg }|jd }t|D ]Q}|||d  }t|d	d
 }| d 	t
j}t|}t }|j|dd |d |dkr{|dd| d|dff q8|dd| d|dff q8|d ur|jd dkrtd|jdd  |jdd krtd|j\}}}tj||ddd}d|   |d d d d df< t|dd	d
 }| d 	t
j}t|}t }|j|dd |d |dd|dff t| tdddtt|	||||||dd d!||
d"I d H }nt| td#ddtt|	||||||dd |
d$I d H }tt|I d H S )%NFr   z(Cannot use a mask without an input imager   r   zUnknown model: r   rS   i  @ )total_pixelsr   r   r   rg   image_z.pngr   zimage[]z%Cannot use a mask with multiple imager   r   r   r   r   r   rj   zmask.pngr   r   r   r   )r   rJ   r   r   rc   rQ   r\   
moderationr   )r   r0   r   r   price_extractorr   )r   r0   r   )r   r2   r   r   r   ranger   r   r   r;   r9   r   r   r   r   r   r   r=   r   r>   zerosr   r   r   r   r   r   r   r   r   rG   )r   rJ   rQ   r   r   rg   rj   rc   r\   r   r   r   
batch_sizeisingle_imagescaled_imager   r   r   _r   r   	rgba_maskscaled_maskmask_npmask_imgmask_img_byte_arrrA   r*   r*   r+   r     s   



"





zOpenAIGPTImage1.execute)r   r   r   NNrS   r]   r   )r   r    r!   r   r   strintr   r   r   r   r   r*   r*   r*   r+   r   h  sB    
i	
r   c                   @   s   e Zd ZdZedd Zededee fddZ	edee de
fd	d
Zeddejde
defddZe		dde
dejdB dee dB deeeB eB  fddZedejjdddfde
dededejdB dee dB dedB dejfddZdS )OpenAIChatNodez?
    Node to generate text responses from an OpenAI model.
    c                 C   s   t jdddddt jjdddd	d
t jjdddddt jjdtddt jjddddt djddddt djddddgt j	 gt j
jt j
jt j
jgdt jt jdgdddd
S ) Nr   zOpenAI ChatGPTapi node/text/OpenAIzText Generationz-Generate text responses from an OpenAI model.rJ   rK   Tz6Text inputs to the model, used to generate a response.rM   persist_contextFz/This parameter is deprecated and has no effect.)rN   rP   advancedr   z'The model used to generate the response)rb   rP   imageszqOptional image(s) to use as context for the model. To include multiple images, you can use the Batch Images node.ri   OPENAI_INPUT_FILESr   zgOptional file(s) to use as context for the model. Accepts inputs from the OpenAI Chat Input Files node.)r[   rP   OPENAI_CHAT_CONFIGadvanced_optionsz`Optional configuration for the model. Accepts inputs from the OpenAI Chat Advanced Options node.rl   au  
                (
                  $m := widgets.model;
                  $contains($m, "o4-mini") ? {
                    "type": "list_usd",
                    "usd": [0.0011, 0.0044],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "o1-pro") ? {
                    "type": "list_usd",
                    "usd": [0.15, 0.6],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "o1") ? {
                    "type": "list_usd",
                    "usd": [0.015, 0.06],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "o3-mini") ? {
                    "type": "list_usd",
                    "usd": [0.0011, 0.0044],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "o3") ? {
                    "type": "list_usd",
                    "usd": [0.01, 0.04],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "gpt-4.1-nano") ? {
                    "type": "list_usd",
                    "usd": [0.0001, 0.0004],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "gpt-4.1-mini") ? {
                    "type": "list_usd",
                    "usd": [0.0004, 0.0016],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "gpt-4.1") ? {
                    "type": "list_usd",
                    "usd": [0.002, 0.008],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "gpt-5-nano") ? {
                    "type": "list_usd",
                    "usd": [0.00005, 0.0004],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "gpt-5-mini") ? {
                    "type": "list_usd",
                    "usd": [0.00025, 0.002],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : $contains($m, "gpt-5") ? {
                    "type": "list_usd",
                    "usd": [0.00125, 0.01],
                    "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                  }
                  : {"type": "text", "text": "Token-based"}
                )
                rn   )
rr   rs   rt   essentials_categoryru   rv   rw   rx   ry   rz   )r   r|   r}   r   Booleanr   r   r   Customr   r   r   r   r   r   r   r   r*   r*   r+   r   <  sf   

#zOpenAIChatNode.define_schemarA   r-   c                 C   s(   |j D ]}|jdkr|j  S qtd)z.Extract message content from the API response.messagez#No output message found in response)outputtypecontent	TypeError)r   rA   r   r*   r*   r+   !get_message_content_from_response  s
   


z0OpenAIChatNode.get_message_content_from_responsemessage_contentc                 C   s&   |D ]}|j dkrt|j  S qdS )z*Extract text content from message content.output_textz No text output found in response)r   r   text)r   r  content_itemr*   r*   r+   get_text_from_message_content  s
   
z,OpenAIChatNode.get_text_from_message_contentr   rg   detail_levelc                 C   s   t |dt| ddS )z2Convert a tensor to an input image content object.data:image/png;base64,input_imagedetail	image_urlr   )r
   r   )r   rg   r  r*   r*   r+   tensor_to_input_image_content  s
   z,OpenAIChatNode.tensor_to_input_image_contentNrJ   r   c              
   C   sh   t |ddg}|dur)t|jd D ]}|tddt|| d dd q|dur2|| |S )	zGCreate a list of input message contents from prompt and optional image.
input_text)r  r   Nr   r   r  r  r	  )r   r   r   r=   r
   r   r   extend)r   rJ   rg   r   content_listr   r*   r*   r+   create_input_message_contents  s   
	
z,OpenAIChatNode.create_input_message_contentsFr   r   r   r   c           
         s   t |dd t| ttddttdt| |||ddgdd|d d|r*|jdd	ni d
I d H }|j	}t
| tt d| dtdd ddgdI d H }	t| | |	S )NFr   r   r   user)r   roleT)inputstorestreamr   previous_response_id)exclude_noner   /)r   c                 S      | j S N)statusr   r*   r*   r+   <lambda>      z(OpenAIChatNode.execute.<locals>.<lambda>
incomplete	completed)r   status_extractorcompleted_statusesr*   )r   r   r   RESPONSES_ENDPOINTr   r   r   r  
model_dumpidr   r   r   r  r   )
r   rJ   r   r   r   r   r   create_responseresponse_idresult_responser*   r*   r+   r     s:   

zOpenAIChatNode.execute)r   NN)r   r    r!   __doc__r   r   r   listr   r   r   r  r>   Tensorr
   r  r	   r   r  r   r'   valueboolr   r   r   r   r*   r*   r*   r+   r   7  sV    
t

r   c                   @   sV   e Zd ZdZedd ZededefddZeg fded	e	e de
jfd
dZdS )OpenAIInputFilesz7
    Loads and formats input files for OpenAI API.
    c                 C   s   t  }dd t|D }t|dd d}dd |D }tjddd	d
tjjd||r.|d ndddt	djddddgt	d
 gdS )z
        For details about the supported file input types, see:
        https://platform.openai.com/docs/guides/pdf-files?api-mode=responses
        c                 S   s>   g | ]}|  r|jd s|jdr| jdk r|qS )z.txtz.pdfi   )is_filer   endswithstatst_size.0fr*   r*   r+   
<listcomp>  s    

z2OpenAIInputFiles.define_schema.<locals>.<listcomp>c                 S   r  r  r   )xr*   r*   r+   r  !  r  z0OpenAIInputFiles.define_schema.<locals>.<lambda>)keyc                 S   s   g | ]}|j qS r*   r7  r3  r*   r*   r+   r6  "  s    r.  zOpenAI ChatGPT Input Filesr   u   Loads and prepares input files (text, pdf, etc.) to include as inputs for the OpenAI Chat Node. The files will be read by the OpenAI model when generating a response. 🛈 TIP: Can be chained together with other OpenAI Input File nodes.filer   NzgInput files to include as context for the model. Only accepts text (.txt) and PDF (.pdf) files for now.)rb   rN   rP   r   zAn optional additional file(s) to batch together with the file loaded from this node. Allows chaining of input files so that a single message can include multiple input files.Tri   rr   rs   rt   ru   rv   rw   )folder_pathsget_input_directoryosscandirsortedr   r|   r   r   r   r   )r   	input_dirinput_filesr*   r*   r+   r     s4   
zOpenAIInputFiles.define_schema	file_pathr-   c                 C   s   t t|tj|ddS )N
input_file)	file_datafilenamer   )r	   r   r>  r   basename)r   rC  r*   r*   r+   create_input_file_content:  s
   
z*OpenAIInputFiles.create_input_file_contentr:  r   c                 C   s(   t |}| |}|g| }t|S )z?
        Loads and formats input files for OpenAI API.
        )r<  get_annotated_filepathrH  r   r   )r   r:  r   rC  input_file_contentr   r*   r*   r+   r   B  s   



zOpenAIInputFiles.executeN)r   r    r!   r)  r   r   r   r	   rH  r*  r   r   r   r*   r*   r*   r+   r.    s    
&&r.  c                
   @   sJ   e Zd ZdZedd Ze		ddededB dedB de	j
fd	d
ZdS )OpenAIChatConfigzAAllows setting additional configuration for the OpenAI Chat Node.c                 C   sd   t jddddt jjdddgddd	d
t jjdddddd	d	dt jjdd	d	ddgt d gdS )NrK  zOpenAI ChatGPT Advanced Optionsr   zKAllows specifying advanced configuration options for the OpenAI Chat Nodes.
truncationr   disableda  The truncation strategy to use for the model response. auto: If the context of this response and previous ones exceeds the model's context window size, the model will truncate the response to fit the context window by dropping input items in the middle of the conversation.disabled: If a model response will exceed the context window size for a model, the request will fail with a 400 errorT)rb   rN   rP   r   max_output_tokens   i   i @  zmAn upper bound for the number of tokens that can be generated for a response, including visible output tokens)rV   rN   rW   rP   r[   r   instructionsz:Instructions for the model on how to generate the response)rO   r[   rP   r   r;  )r   r|   r   r   r~   r}   r   r   r   r*   r*   r+   r   P  s>   	zOpenAIChatConfig.define_schemaNrL  rP  rN  r-   c                 C   s   t t|||dS )af  
        Configure advanced options for the OpenAI Chat Node.

        Note:
            While `top_p` and `temperature` are listed as properties in the
            spec, they are not supported for all models (e.g., o4-mini).
            They are not exposed as inputs at all to avoid having to manually
            remove depending on model choice.
        )rP  rL  rN  )r   r   r   )r   rL  rP  rN  r*   r*   r+   r   t  s   zOpenAIChatConfig.executer(  )r   r    r!   r)  r   r   r-  r   r   r   r   r   r*   r*   r*   r+   rK  M  s     
#rK  c                   @   s(   e Zd Zedeeej  fddZdS )OpenAIExtensionr-   c                    s   t tttttgS r  )rH   r   r   r   r.  rK  )selfr*   r*   r+   get_node_list  s   zOpenAIExtension.get_node_listN)	r   r    r!   r   r*  r   r   	ComfyNoderS  r*   r*   r*   r+   rQ    s    rQ  c                      s   t  S r  )rQ  r*   r*   r*   r+   comfy_entrypoint  s   rU  r  ):r4   r>  enumr   ior   r   r9   r>   PILr   typing_extensionsr   r<  comfy_api.latestr   r   r   comfy_api_nodes.apis.openair	   r
   r   r   r   r   r   r   r   r   r   comfy_api_nodes.utilr   r   r   r   r   r   r   r   r"  STARTING_POINT_ID_PATTERNr   r   r   r+  rG   rT  rH   r   floatr   r   r   r   r.  rK  rQ  rU  r*   r*   r*   r+   <module>   s:    4(& m P X?@