o
    ip                     @   s|   d Z ddlZddlZddlmZ ddlZddlmZmZm	Z	 ddl
mZ ddlmZ ddlmZmZ e ZG dd	 d	ZdS )
z7Main project generator that orchestrates file creation.    N)Path)EnvironmentPackageLoaderselect_autoescape)Console)ProjectConfig)ServiceLoaderServiceRegistryc                   @   s  e Zd ZdZdefddZdedefddZd6ded	B de	defddZ
dedd	fddZde	fddZdedd	fddZdedd	fddZdedd	fddZdedd	fddZded	B deded	B fddZdedd	fdd Zdedd	fd!d"Zdedd	fd#d$Zdedd	fd%d&Zdeeeef  fd'd(Zd)edd	fd*d+Zd,ed-edd	fd.d/Zd0ed1edd	fd2d3Zdedd	fd4d5Zd	S )7ProjectGeneratorz8Generates a complete Pipecat project from configuration.configc                 C   s$   || _ ttddt ddd| _dS )z~
        Initialize the project generator.

        Args:
            config: Project configuration from user prompts
        pipecat_cli	templatesT)loader
autoescapetrim_blockslstrip_blocksN)r   r   r   r   env)selfr    r   R/home/ubuntu/.local/lib/python3.10/site-packages/pipecat_cli/generators/project.py__init__   s   zProjectGenerator.__init__
output_dirreturnc                 C   s\   	 t d| jj d tjd| jj ddd d }|s#td	|| }| s-|S q)
a  
        Prompt user for a new project name if the current one already exists.

        Args:
            output_dir: The output directory where projects are created

        Returns:
            A valid project name that doesn't conflict with existing directories
        Tu   
[yellow]⚠️  Directory 'z' already exists![/yellow]z&Please enter a different project name:z-newc                 S   s   t | dkpdS )Nr   zProject name cannot be empty)len)textr   r   r   <lambda>:   s    z7ProjectGenerator._prompt_for_new_name.<locals>.<lambda>)defaultvalidatezProject creation cancelled)	consoleprintr   project_namequestionaryr   askKeyboardInterruptexists)r   r   new_namenew_pathr   r   r   _prompt_for_new_name)   s"   
z%ProjectGenerator._prompt_for_new_nameNFnon_interactivec                 C   sL  |du rt  }|| jj }| r-|r td| jj d| | |}|| j_|| }|jddd |d }|jdd | jjrL|d }|jdd | 	| d	| jj
v sid
| jj
v sid| jj
v sid| jj
v rn| | | | | | | | | | | jjr| | | jjr| | | jjr| |d  | | |S )a  
        Generate the complete project structure.

        Args:
            output_dir: Optional directory to create project in (defaults to current dir)
            non_interactive: If True, raise FileExistsError instead of prompting

        Returns:
            Path to the created project directory

        Raises:
            FileExistsError: If directory exists and non_interactive is True
        NzDirectory 'z' already exists in Tparentsexist_okserver)r+   clientdaily_pstn_dialindaily_pstn_dialouttwilio_daily_sip_dialintwilio_daily_sip_dialout)r   cwdr   r    r$   FileExistsErrorr'   mkdirgenerate_client_generate_bot_file
transports_generate_server_files_generate_pyproject_generate_env_example_generate_gitignore_generate_readmedeploy_to_cloud_generate_dockerfile_generate_pcc_deploy_generate_client_format_python_files)r   r   r(   project_pathr%   server_pathclient_pathr   r   r   generateG   sF   









zProjectGenerator.generaterB   c           	      C   s   | j jr%| j jdkrdS | j j}d|dd d}d|dd d}n| j jrB| j j}d|dd d}d	|dd d}ndS | j|}| }|d
 | | j|}| }|d | dS )zUGenerate server.py and server_utils.py for Daily PSTN dial-out or Twilio + Daily SIP.zdial-inNzserver/server_pstn_- z
.py.jinja2zserver/server_utils_pstn_zserver/server_twilio_daily_sip_z%server/server_utils_twilio_daily_sip_z	server.pyzserver_utils.py)r   daily_pstn_modereplacetwilio_daily_sip_moder   get_templaterender
write_text)	r   rB   modeserver_template_nameutils_template_nameserver_templateserver_contentutils_templateutils_contentr   r   r   r8      s&   z'ProjectGenerator._generate_server_filesc                 C   s   g }| j jdkr*| j jr|| j j | j jr|| j j | j jr)|| j j n| j jr5|| j j | j jr@|| j j |D ]}|tj	v rYtj	| }d|v sVd|v rY dS qBdS )z:Check if any selected service requires an aiohttp session.cascadezsession=zaiohttp_session=TF)
r   rN   stt_serviceappendllm_servicetts_servicerealtime_servicevideo_servicer	   SERVICE_CONFIGS)r   service_valuesservice_value
config_strr   r   r   _needs_aiohttp_session   s(   

z'ProjectGenerator._needs_aiohttp_sessionc           
      C   s  | j jdkr| jd}n| jd}d| j ji}| j jdkr/|| j j| j j| j jd n| j j	|d< | j j
r?| j j
|d< | j j| j j| j jd}t||| j j}|  }|rdd	|vrd|d
d	 i d| j jd|d| j jd| j jd| j jd| j jd| j jd| j jd| j j	d| j j
d| j jd| j jd| j jd| j jd| j jd| j jdtj| j j| j j|d}|jdi |}|d }	|	| dS )zGenerate the main bot.py file.rU   zserver/bot_cascade.py.jinja2zserver/bot_realtime.py.jinja2r7   sttllmttsrealtimevideo)	recordingtranscriptionobservabilityzimport aiohttpr   r    importsbot_typerN   rV   rX   rY   rZ   r[   video_inputvideo_outputrg   rh   enable_krispenable_observabilityservice_configs)rH   rJ   needs_sessionzbot.pyNr   )r   rN   r   rK   r7   updaterV   rX   rY   rZ   r[   rg   rh   ro   r   get_imports_for_servicesrk   r`   insertr    rl   rm   rn   r	   r\   rH   rJ   rL   rM   )
r   rB   templateservicesfeaturesrj   rq   contextcontentbot_filer   r   r   r6      s   	
z#ProjectGenerator._generate_bot_filec           	      C   s   | j d}d| jji}| jjdkr"|| jj| jj| jjd n| jj	|d< t
|}dt|}d| d}| jj|| jj| jj| jjd	}|jdi |}|d
 | dS )z*Generate pyproject.toml with dependencies.zserver/pyproject.toml.jinja2r7   rU   ra   re   ,zpipecat-ai[])r    pipecat_dependencyr=   ro   r7   zpyproject.tomlNr   )r   rK   r   r7   rN   rr   rV   rX   rY   rZ   r   extract_extras_for_servicesjoinsortedr    r=   ro   rL   rM   )	r   rB   ru   rv   extraspipecat_extrasr}   rx   ry   r   r   r   r9     s*   
z$ProjectGenerator._generate_pyprojectc              
   C   sj   | j d}| jj| jj| jj| jj| jj| jj| jj	| jj
| jjd	}|jdi |}|d | dS )z-Generate .env.example with required API keys.zserver/env.example.jinja2)	r    r7   rV   rX   rY   rZ   r[   rH   rJ   z.env.exampleNr   )r   rK   r   r    r7   rV   rX   rY   rZ   r[   rH   rJ   rL   rM   r   rB   ru   rx   ry   r   r   r   r:   C  s   z&ProjectGenerator._generate_env_examplec                 C   s:   | j d}d| jji}|jdi |}|d | dS )zGenerate .gitignore file.zgitignore.jinja2r5   z
.gitignoreNr   )r   rK   r   r5   rL   rM   r   r   r   r   r;   V  s
   z$ProjectGenerator._generate_gitignorer^   service_listc                    s     sdS t  fdd|D  S )z-Get human-readable label for a service value.Nc                 3   s     | ]}|j  kr|jV  qd S N)valuelabel).0svcr^   r   r   	<genexpr>d  s    z6ProjectGenerator._get_service_label.<locals>.<genexpr>)next)r   r^   r   r   r   r   _get_service_label_  s
   z#ProjectGenerator._get_service_labelc                    s  j d}tjtj   }h dddhtfddjjD }tfddjjD }i djj	d	jj
d
jjd fddjjD djjdjjdjjtjdjjdjjtjdjjdjjtjdjjdjjtjdjjdjjdjjdjjjjjjjjjjjjjj|||jjjj d}|j!di |}|d "| dS )z6Generate README.md with project-specific instructions.zREADME.md.jinja2   plivoexoteltelnyxtwiliosmallwebrtcdailyc                 3       | ]}| v V  qd S r   r   r   ttelephony_transportsr   r   r   r      z4ProjectGenerator._generate_readme.<locals>.<genexpr>c                 3   r   r   r   r   webrtc_transportsr   r   r   s  r   r    rk   r7   transport_labelsc                    s   g | ]} | qS r   )r   r   )all_transportsr   r   r   
<listcomp>y  s    z5ProjectGenerator._generate_readme.<locals>.<listcomp>rN   rV   	stt_labelrX   	llm_labelrY   	tts_labelrZ   realtime_labelrl   rm   rg   rh   )rn   ro   r=   r5   client_frameworkclient_serverrun_commandshas_telephony
has_webrtcrH   rJ   z	README.mdNr   )#r   rK   r	   WEBRTC_TRANSPORTSTELEPHONY_TRANSPORTS_get_run_commandsanyr   r7   r    rk   rN   rV   r   STT_SERVICESrX   LLM_SERVICESrY   TTS_SERVICESrZ   REALTIME_SERVICESrl   rm   rg   rh   rn   ro   r=   r5   r   r   rH   rJ   rL   rM   )r   rB   ru   r   r   r   rx   ry   r   )r   r   r   r   r   r<   g  s   	



)z!ProjectGenerator._generate_readmec                 C   sF   | j d}| jj| jj| jjd}|jdi |}|d | dS )z1Generate Dockerfile for Pipecat Cloud deployment.zserver/Dockerfile.jinja2)r7   rH   rJ   
DockerfileNr   )r   rK   r   r7   rH   rJ   rL   rM   r   r   r   r   r>     s   z%ProjectGenerator._generate_dockerfilec                 C   s@   | j d}| jj| jjd}|jdi |}|d | dS )z6Generate pcc-deploy.toml for Pipecat Cloud deployment.zserver/pcc-deploy.toml.jinja2)r    rn   zpcc-deploy.tomlNr   )r   rK   r   r    rn   rL   rM   r   r   r   r   r?     s   z%ProjectGenerator._generate_pcc_deployc           
         s0  t d t d| d t d t d| jj d tdd | jjD }td	d | jjD }|s8|rrt d
 t d t d t d t d t d t d t d | jjrkt d dS t d dS |  }| jjrt d t d t d t d t d
 t d t d t d t d h d ddht fdd| jjD }tfdd| jjD }dd |D }d d |D }|r"|r"t d! t d" |D ]}	t d#|	d$  d%|	d&  d qt d' t d( t d) |D ]}	t d*|	d$  d%|	d&  d qnc|rXt d+ t d, |D ]%}	|	d$ rJt d-|	d$  d%|	d&  d q1t d.|	d&  d q1n-t d, |D ]%}	|	d$ rxt d-|	d$  d%|	d&  d q_t d.|	d&  d q_| jjrt d dS t d/ dS )0zPrint next steps for the user.u;   
[bold green]✨ Project created successfully![/bold green]z	   [cyan]z[/cyan]
z[bold]Next steps:[/bold]
u(     • Go to your project: [bold cyan]cd z[/bold cyan]c                 s       | ]}|d v V  qdS ))r.   r/   Nr   r   r   r   r   r     s    
z4ProjectGenerator.print_next_steps.<locals>.<genexpr>c                 s   r   ))r0   r1   Nr   r   r   r   r   r     s
    
z
  [bold]Server setup:[/bold]u4     • Go to server: [bold cyan]cd server[/bold cyan]u:     • Install dependencies: [bold cyan]uv sync[/bold cyan]uC     • Create .env file: [bold cyan]cp .env.example .env[/bold cyan]u2     • [bold]Edit .env and add your API keys[/bold]z=
  [bold]See README.md for detailed setup instructions[/bold]u?     • Configure webhooks/SIP domains as described in the READMEu:     • Run the multi-terminal workflow (server.py + bot.py)zV
[dim]See https://docs.pipecat.ai/deployment/pipecat-cloud for deployment info.[/dim]
zN
[dim]Check the README for local development and production deployment.[/dim]
Nz
  [bold]Client setup:[/bold]uY     • Go to client: In a separate terminal window or tab [bold cyan]cd client[/bold cyan]u>     • Install dependencies: [bold cyan]npm install[/bold cyan]u8     • Run dev server: [bold cyan]npm run dev[/bold cyan]r   r   r   c                 3   r   r   r   r   r   r   r   r     r   c                 3   r   r   r   r   r   r   r   r     r   c                 S      g | ]
}|d  dv r|qS )r   )SmallWebRTCDailyr   r   cmdr   r   r   r     s    z5ProjectGenerator.print_next_steps.<locals>.<listcomp>c                 S   r   )r   )TwilioTelnyxPlivoExotelr   r   r   r   r   r     s    u     • Run your bot:
z(     [bold]For local development:[/bold]u          • r   z: [bold cyan]commandz,
     [bold]For telephony deployment:[/bold]u<          • Run ngrok: [bold cyan]ngrok http 7860[/bold cyan]u          • Run bot:u            • u>     • Run ngrok tunnel: [bold cyan]ngrok http 7860[/bold cyan]u     • Run your bot:u	        • z     [bold cyan]z;
[dim]See README.md for detailed setup instructions.[/dim]
)	r   r   r   r    r   r7   r=   r   r5   )
r   rB   is_daily_pstnis_twilio_daily_sipr   r   r   webrtc_cmdstelephony_cmdsr   r   )r   r   r   print_next_steps  s   



















"


$


$

$
z!ProjectGenerator.print_next_stepsc                 C   s   g }| j jD ].}|dkr|ddd q|dkr"|ddd q|dv r4|| d	| d
d q|s?|ddd |S )z0Get transport-specific run commands with labels.r   r   zuv run bot.py)r   r   r   r   zuv run bot.py --transport dailyr   zuv run bot.py --transport z --proxy your_url.ngrok.iorG   )r   r7   rW   title)r   commands	transportr   r   r   r   '  s    
z"ProjectGenerator._get_run_commandsrD   c                 C   s   | j jdkr%| j jdkrd}n,| j jdkrd}n#td| j j d dS | j jd	kr.d
}ntd| j j d dS ddl}t|jj}|d | }|	 sZtd| d dS | 
|| dS )z"Generate client application files.reactvitezclient/react-vitenextjszclient/react-nextjsu'   [yellow]⚠️  Unknown client server: z	[/yellow]Nvanillazclient/vanilla-js-viteu*   [yellow]⚠️  Unknown client framework: r   r   u$   [yellow]⚠️  Template not found: )r   r   r   r   r   r   r   __file__parentr$   _copy_and_render_directory)r   rD   template_dirr   package_pathsource_template_dirr   r   r   r@   >  s,   z!ProjectGenerator._generate_client
source_dirdest_dirc                 C   s   | dD ]I}| rN||}d| jjvrdt|v rq|jdkr0|t|dtd   }n|| }|jj	ddd |jdkrH| 
|| qt|| qdS )z
        Recursively copy directory contents, rendering .jinja2 templates.

        Args:
            source_dir: Source template directory
            dest_dir: Destination directory
        *r   zapi/sessionsz.jinja2NTr)   )rglobis_filerelative_tor   r7   strsuffixr   r   r4   _render_client_templateshutilcopy2)r   r   r   itemrel_path	dest_filer   r   r   r   b  s   


z+ProjectGenerator._copy_and_render_directorytemplate_filer   c           
   
   C   s   |  }ddlm} z||}W n  ty0 } ztd|j d td| d  d}~ww dd	 | jjD }| jj	|d
}|j
di |}	||	 dS )aJ  
        Render a Jinja2 template file with client-specific context.

        Only used for config.ts and package.json templates.
        Most TypeScript files are static and copied directly.

        Args:
            template_file: Path to .jinja2 template
            dest_file: Destination file path (without .jinja2)
        r   )Templatez[red]Error rendering template z:[/red]z[red]z[/red]Nc                 S   s   g | ]}d |iqS )r   r   r   r   r   r   r     s    z<ProjectGenerator._render_client_template.<locals>.<listcomp>)r    r7   r   )	read_textjinja2r   	Exceptionr   r   namer   r7   r    rL   rM   )
r   r   r   template_contentr   ru   etransport_objectsrx   renderedr   r   r   r     s    z(ProjectGenerator._render_client_templatec                 C   sV   z t jddt|gddd t jddddd	t|gddd W d
S  ty*   Y d
S w )z(Format generated Python files with Ruff.ruffformatTF)capture_outputcheckr   z--fixz--selectIN)
subprocessrunr   FileNotFoundError)r   rB   r   r   r   rA     s   z%ProjectGenerator._format_python_files)NF)__name__
__module____qualname____doc__r   r   r   r   r'   boolrE   r8   r`   r6   r9   r:   r;   listr   r<   r>   r?   r   dictr   r@   r   r   rA   r   r   r   r   r
      s*    R"F%	:m$##r
   )r   r   r   pathlibr   r!   r   r   r   r   rich.consoler   pipecat_cli.promptsr   pipecat_cli.registryr   r	   r   r
   r   r   r   r   <module>   s   