Agentic-AI-CHAT / app.py
ginipick's picture
Update app.py
cbba31a verified
raw
history blame
51.9 kB
#!/usr/bin/env python
import os
import re
import tempfile
import gc # Added garbage collector
from collections.abc import Iterator
from threading import Thread
import json
import requests
import cv2
import base64
import logging
import time
from urllib.parse import quote # Added for URL encoding
import importlib # For dynamic import
import gradio as gr
import spaces
import torch
from loguru import logger
from PIL import Image
from transformers import AutoProcessor, Gemma3ForConditionalGeneration, TextIteratorStreamer
# CSV/TXT/PDF analysis
import pandas as pd
import PyPDF2
# =============================================================================
# (New) Image API related functions
# =============================================================================
from gradio_client import Client
API_URL = "http://211.233.58.201:7896"
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def test_api_connection() -> str:
"""Test API server connection"""
try:
client = Client(API_URL)
return "API connection successful: Operating normally"
except Exception as e:
logging.error(f"API connection test failed: {e}")
return f"API connection failed: {e}"
def generate_image(prompt: str, width: float, height: float, guidance: float, inference_steps: float, seed: float):
"""Image generation function (flexible return types)"""
if not prompt:
return None, "Error: A prompt is required."
try:
logging.info(f"Calling image generation API with prompt: {prompt}")
client = Client(API_URL)
result = client.predict(
prompt=prompt,
width=int(width),
height=int(height),
guidance=float(guidance),
inference_steps=int(inference_steps),
seed=int(seed),
do_img2img=False,
init_image=None,
image2image_strength=0.8,
resize_img=True,
api_name="/generate_image"
)
logging.info(f"Image generation result: {type(result)}, length: {len(result) if isinstance(result, (list, tuple)) else 'unknown'}")
# Handle cases where the result is a tuple or list
if isinstance(result, (list, tuple)) and len(result) > 0:
image_data = result[0] # The first element is the image data
seed_info = result[1] if len(result) > 1 else "Unknown seed"
return image_data, seed_info
else:
# When a single value is returned
return result, "Unknown seed"
except Exception as e:
logging.error(f"Image generation failed: {str(e)}")
return None, f"Error: {str(e)}"
def fix_base64_padding(data):
"""Fix the padding of a Base64 string."""
if isinstance(data, bytes):
data = data.decode('utf-8')
if "base64," in data:
data = data.split("base64,", 1)[1]
missing_padding = len(data) % 4
if missing_padding:
data += '=' * (4 - missing_padding)
return data
def clear_cuda_cache():
"""Explicitly clear the CUDA cache."""
if torch.cuda.is_available():
torch.cuda.empty_cache()
gc.collect()
SERPHOUSE_API_KEY = os.getenv("SERPHOUSE_API_KEY", "")
def extract_keywords(text: str, top_k: int = 5) -> str:
"""Simple keyword extraction: only keep English, Korean, numbers, and spaces."""
text = re.sub(r"[^a-zA-Z0-9๊ฐ€-ํžฃ\s]", "", text)
tokens = text.split()
return " ".join(tokens[:top_k])
def do_web_search(query: str) -> str:
"""Call the SerpHouse LIVE API to return Markdown formatted search results"""
try:
url = "https://api.serphouse.com/serp/live"
params = {
"q": query,
"domain": "google.com",
"serp_type": "web",
"device": "desktop",
"lang": "en",
"num": "20"
}
headers = {"Authorization": f"Bearer {SERPHOUSE_API_KEY}"}
logger.info(f"Calling SerpHouse API with query: {query}")
response = requests.get(url, headers=headers, params=params, timeout=60)
response.raise_for_status()
data = response.json()
results = data.get("results", {})
organic = None
if isinstance(results, dict) and "organic" in results:
organic = results["organic"]
elif isinstance(results, dict) and "results" in results:
if isinstance(results["results"], dict) and "organic" in results["results"]:
organic = results["results"]["organic"]
elif "organic" in data:
organic = data["organic"]
if not organic:
logger.warning("Organic results not found in response.")
return "No web search results available or the API response structure is unexpected."
max_results = min(20, len(organic))
limited_organic = organic[:max_results]
summary_lines = []
for idx, item in enumerate(limited_organic, start=1):
title = item.get("title", "No Title")
link = item.get("link", "#")
snippet = item.get("snippet", "No Description")
displayed_link = item.get("displayed_link", link)
summary_lines.append(
f"### Result {idx}: {title}\n\n"
f"{snippet}\n\n"
f"**Source**: [{displayed_link}]({link})\n\n"
f"---\n"
)
instructions = """
# Web Search Results
Below are the search results. Use this information to answer the query:
1. Refer to each result's title, description, and source link.
2. In your answer, explicitly cite the source of any used information (e.g., "[Source Title](link)").
3. Include the actual source links in your response.
4. Synthesize information from multiple sources.
5. At the end include a "References:" section listing the main source links.
"""
return instructions + "\n".join(summary_lines)
except Exception as e:
logger.error(f"Web search failed: {e}")
return f"Web search failed: {str(e)}"
MAX_CONTENT_CHARS = 2000
MAX_INPUT_LENGTH = 2096
model_id = os.getenv("MODEL_ID", "VIDraft/Gemma-3-R1984-4B")
processor = AutoProcessor.from_pretrained(model_id, padding_side="left")
model = Gemma3ForConditionalGeneration.from_pretrained(
model_id,
device_map="auto",
torch_dtype=torch.bfloat16,
attn_implementation="eager"
)
MAX_NUM_IMAGES = int(os.getenv("MAX_NUM_IMAGES", "5"))
def analyze_csv_file(path: str) -> str:
try:
df = pd.read_csv(path)
if df.shape[0] > 50 or df.shape[1] > 10:
df = df.iloc[:50, :10]
df_str = df.to_string()
if len(df_str) > MAX_CONTENT_CHARS:
df_str = df_str[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
return f"**[CSV File: {os.path.basename(path)}]**\n\n{df_str}"
except Exception as e:
return f"CSV file read failed ({os.path.basename(path)}): {str(e)}"
def analyze_txt_file(path: str) -> str:
try:
with open(path, "r", encoding="utf-8") as f:
text = f.read()
if len(text) > MAX_CONTENT_CHARS:
text = text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
return f"**[TXT File: {os.path.basename(path)}]**\n\n{text}"
except Exception as e:
return f"TXT file read failed ({os.path.basename(path)}): {str(e)}"
def pdf_to_markdown(pdf_path: str) -> str:
text_chunks = []
try:
with open(pdf_path, "rb") as f:
reader = PyPDF2.PdfReader(f)
max_pages = min(5, len(reader.pages))
for page_num in range(max_pages):
page_text = reader.pages[page_num].extract_text() or ""
page_text = page_text.strip()
if page_text:
if len(page_text) > MAX_CONTENT_CHARS // max_pages:
page_text = page_text[:MAX_CONTENT_CHARS // max_pages] + "...(truncated)"
text_chunks.append(f"## Page {page_num+1}\n\n{page_text}\n")
if len(reader.pages) > max_pages:
text_chunks.append(f"\n...(Displaying only {max_pages} out of {len(reader.pages)} pages)...")
except Exception as e:
return f"PDF file read failed ({os.path.basename(pdf_path)}): {str(e)}"
full_text = "\n".join(text_chunks)
if len(full_text) > MAX_CONTENT_CHARS:
full_text = full_text[:MAX_CONTENT_CHARS] + "\n...(truncated)..."
return f"**[PDF File: {os.path.basename(pdf_path)}]**\n\n{full_text}"
def count_files_in_new_message(paths: list[str]) -> tuple[int, int]:
image_count = 0
video_count = 0
for path in paths:
if path.endswith(".mp4"):
video_count += 1
elif re.search(r"\.(png|jpg|jpeg|gif|webp)$", path, re.IGNORECASE):
image_count += 1
return image_count, video_count
def count_files_in_history(history: list[dict]) -> tuple[int, int]:
image_count = 0
video_count = 0
for item in history:
if item["role"] != "user" or isinstance(item["content"], str):
continue
if isinstance(item["content"], list) and len(item["content"]) > 0:
file_path = item["content"][0]
if isinstance(file_path, str):
if file_path.endswith(".mp4"):
video_count += 1
elif re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE):
image_count += 1
return image_count, video_count
def validate_media_constraints(message: dict, history: list[dict]) -> bool:
media_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE) or f.endswith(".mp4")]
new_image_count, new_video_count = count_files_in_new_message(media_files)
history_image_count, history_video_count = count_files_in_history(history)
image_count = history_image_count + new_image_count
video_count = history_video_count + new_video_count
if video_count > 1:
gr.Warning("Only one video file is supported.")
return False
if video_count == 1:
if image_count > 0:
gr.Warning("Mixing images and a video is not allowed.")
return False
if "<image>" in message["text"]:
gr.Warning("The <image> tag cannot be used together with a video file.")
return False
if video_count == 0 and image_count > MAX_NUM_IMAGES:
gr.Warning(f"You can upload a maximum of {MAX_NUM_IMAGES} images.")
return False
if "<image>" in message["text"]:
image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
image_tag_count = message["text"].count("<image>")
if image_tag_count != len(image_files):
gr.Warning("The number of <image> tags does not match the number of image files provided.")
return False
return True
def downsample_video(video_path: str) -> list[tuple[Image.Image, float]]:
vidcap = cv2.VideoCapture(video_path)
fps = vidcap.get(cv2.CAP_PROP_FPS)
total_frames = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
frame_interval = max(int(fps), int(total_frames / 10))
frames = []
for i in range(0, total_frames, frame_interval):
vidcap.set(cv2.CAP_PROP_POS_FRAMES, i)
success, image = vidcap.read()
if success:
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.resize(image, (0, 0), fx=0.5, fy=0.5)
pil_image = Image.fromarray(image)
timestamp = round(i / fps, 2)
frames.append((pil_image, timestamp))
if len(frames) >= 5:
break
vidcap.release()
return frames
def process_video(video_path: str) -> tuple[list[dict], list[str]]:
content = []
temp_files = []
frames = downsample_video(video_path)
for pil_image, timestamp in frames:
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file:
pil_image.save(temp_file.name)
temp_files.append(temp_file.name)
content.append({"type": "text", "text": f"Frame {timestamp}:"})
content.append({"type": "image", "url": temp_file.name})
return content, temp_files
def process_interleaved_images(message: dict) -> list[dict]:
parts = re.split(r"(<image>)", message["text"])
content = []
image_files = [f for f in message["files"] if re.search(r"\.(png|jpg|jpeg|gif|webp)$", f, re.IGNORECASE)]
image_index = 0
for part in parts:
if part == "<image>" and image_index < len(image_files):
content.append({"type": "image", "url": image_files[image_index]})
image_index += 1
elif part.strip():
content.append({"type": "text", "text": part.strip()})
else:
if isinstance(part, str) and part != "<image>":
content.append({"type": "text", "text": part})
return content
def is_image_file(file_path: str) -> bool:
return bool(re.search(r"\.(png|jpg|jpeg|gif|webp)$", file_path, re.IGNORECASE))
def is_video_file(file_path: str) -> bool:
return file_path.endswith(".mp4")
def is_document_file(file_path: str) -> bool:
return file_path.lower().endswith(".pdf") or file_path.lower().endswith(".csv") or file_path.lower().endswith(".txt")
def process_new_user_message(message: dict) -> tuple[list[dict], list[str]]:
temp_files = []
if not message["files"]:
return [{"type": "text", "text": message["text"]}], temp_files
video_files = [f for f in message["files"] if is_video_file(f)]
image_files = [f for f in message["files"] if is_image_file(f)]
csv_files = [f for f in message["files"] if f.lower().endswith(".csv")]
txt_files = [f for f in message["files"] if f.lower().endswith(".txt")]
pdf_files = [f for f in message["files"] if f.lower().endswith(".pdf")]
content_list = [{"type": "text", "text": message["text"]}]
for csv_path in csv_files:
content_list.append({"type": "text", "text": analyze_csv_file(csv_path)})
for txt_path in txt_files:
content_list.append({"type": "text", "text": analyze_txt_file(txt_path)})
for pdf_path in pdf_files:
content_list.append({"type": "text", "text": pdf_to_markdown(pdf_path)})
if video_files:
video_content, video_temp_files = process_video(video_files[0])
content_list += video_content
temp_files.extend(video_temp_files)
return content_list, temp_files
if "<image>" in message["text"] and image_files:
interleaved_content = process_interleaved_images({"text": message["text"], "files": image_files})
if content_list and content_list[0]["type"] == "text":
content_list = content_list[1:]
return interleaved_content + content_list, temp_files
else:
for img_path in image_files:
content_list.append({"type": "image", "url": img_path})
return content_list, temp_files
def process_history(history: list[dict]) -> list[dict]:
messages = []
current_user_content = []
for item in history:
if item["role"] == "assistant":
if current_user_content:
messages.append({"role": "user", "content": current_user_content})
current_user_content = []
messages.append({"role": "assistant", "content": [{"type": "text", "text": item["content"]}]})
else:
content = item["content"]
if isinstance(content, str):
current_user_content.append({"type": "text", "text": content})
elif isinstance(content, list) and len(content) > 0:
file_path = content[0]
if is_image_file(file_path):
current_user_content.append({"type": "image", "url": file_path})
else:
current_user_content.append({"type": "text", "text": f"[File: {os.path.basename(file_path)}]"})
if current_user_content:
messages.append({"role": "user", "content": current_user_content})
return messages
def _model_gen_with_oom_catch(**kwargs):
try:
model.generate(**kwargs)
except torch.cuda.OutOfMemoryError:
raise RuntimeError("[OutOfMemoryError] Insufficient GPU memory.")
finally:
clear_cuda_cache()
# =============================================================================
# JSON ๊ธฐ๋ฐ˜ ํ•จ์ˆ˜ ๋ชฉ๋ก ๋กœ๋“œ
# =============================================================================
def load_function_definitions(json_path="functions.json"):
"""
๋กœ์ปฌ JSON ํŒŒ์ผ์—์„œ ํ•จ์ˆ˜ ์ •์˜ ๋ชฉ๋ก์„ ๋กœ๋“œํ•˜์—ฌ ๋ฐ˜ํ™˜.
"""
try:
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
func_dict = {}
for entry in data:
func_name = entry["name"]
func_dict[func_name] = entry
return func_dict
except Exception as e:
logger.error(f"Failed to load function definitions from JSON: {e}")
return {}
FUNCTION_DEFINITIONS = load_function_definitions("functions.json")
def handle_function_call(text: str) -> str:
"""
Detects and processes function call blocks in the text using the JSON-based approach.
The model is expected to produce something like:
```tool_code
get_stock_price(ticker="AAPL")
```
or
```tool_code
get_product_name_by_PID(PID="807ZPKBL9V")
```
"""
import re
pattern = r"```tool_code\s*(.*?)\s*```"
match = re.search(pattern, text, re.DOTALL)
if not match:
return ""
code_block = match.group(1).strip()
func_match = re.match(r'^(\w+)\((.*)\)$', code_block)
if not func_match:
logger.debug("No valid function call format found.")
return ""
func_name = func_match.group(1)
param_str = func_match.group(2).strip()
# JSON์—์„œ ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ์ •์˜๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ
if func_name not in FUNCTION_DEFINITIONS:
logger.warning(f"Function '{func_name}' not found in definitions.")
return "```tool_output\nError: Function not found.\n```"
func_info = FUNCTION_DEFINITIONS[func_name]
module_path = func_info["module_path"]
module_func_name = func_info["func_name_in_module"]
try:
imported_module = importlib.import_module(module_path)
except ImportError as e:
logger.error(f"Failed to import module {module_path}: {e}")
return f"```tool_output\nError: Cannot import module '{module_path}'\n```"
if not hasattr(imported_module, module_func_name):
logger.error(f"Module '{module_path}' has no attribute '{module_func_name}'.")
return f"```tool_output\nError: Function '{module_func_name}' not found in module '{module_path}'\n```"
real_func = getattr(imported_module, module_func_name)
# ๊ฐ„๋‹จ ํŒŒ๋ผ๋ฏธํ„ฐ ํŒŒ์‹ฑ (key="value" or key=123)
param_pattern = r'(\w+)\s*=\s*"(.*?)"|(\w+)\s*=\s*([\d.]+)'
param_dict = {}
for p_match in re.finditer(param_pattern, param_str):
if p_match.group(1) and p_match.group(2):
key = p_match.group(1)
val = p_match.group(2)
param_dict[key] = val
else:
key = p_match.group(3)
val = p_match.group(4)
if '.' in val:
param_dict[key] = float(val)
else:
param_dict[key] = int(val)
try:
result = real_func(**param_dict)
except Exception as e:
logger.error(f"Error executing function '{func_name}': {e}")
return f"```tool_output\nError: {str(e)}\n```"
return f"```tool_output\n{result}\n```"
@spaces.GPU(duration=120)
def run(
message: dict,
history: list[dict],
system_prompt: str = "",
max_new_tokens: int = 512,
use_web_search: bool = False,
web_search_query: str = "",
age_group: str = "20s",
mbti_personality: str = "INTP",
sexual_openness: int = 2,
image_gen: bool = False
) -> Iterator[str]:
if not validate_media_constraints(message, history):
yield ""
return
temp_files = []
try:
# JSON์—์„œ ๋กœ๋“œ๋œ ํ•จ์ˆ˜ ์ •๋ณด ๋ฌธ์ž์—ดํ™” (์˜ˆ: ํ•จ์ˆ˜๋ช…๊ณผ example_usage๋งŒ)
available_funcs_text = ""
for f_name, info in FUNCTION_DEFINITIONS.items():
example_usage = info.get("example_usage", "")
available_funcs_text += f"\n\nFunction: {f_name}\nDescription: {info['description']}\nExample:\n{example_usage}\n"
persona = (
f"{system_prompt.strip()}\n\n"
f"Gender: Female\n"
f"Age Group: {age_group}\n"
f"MBTI Persona: {mbti_personality}\n"
f"Sexual Openness (1-5): {sexual_openness}\n\n"
"Below are the available functions you can call.\n"
"Important: Use the format exactly like: ```tool_code\nfunctionName(param=\"string\", ...)\n```\n"
"(Strings must be in double quotes)\n"
f"{available_funcs_text}\n"
)
combined_system_msg = f"[System Prompt]\n{persona.strip()}\n\n"
if use_web_search:
user_text = message["text"]
ws_query = extract_keywords(user_text)
if ws_query.strip():
logger.info(f"[Auto web search keywords] {ws_query!r}")
ws_result = do_web_search(ws_query)
combined_system_msg += f"[Search Results (Top 20 Items)]\n{ws_result}\n\n"
combined_system_msg += (
"[Note: In your answer, cite the above search result links as sources]\n"
"[Important Instructions]\n"
"1. Include a citation in the format \"[Source Title](link)\" for any information from the search results.\n"
"2. Synthesize information from multiple sources when answering.\n"
"3. At the end, add a \"References:\" section listing the main source links.\n"
)
else:
combined_system_msg += "[No valid keywords found; skipping web search]\n\n"
messages = []
if combined_system_msg.strip():
messages.append({"role": "system", "content": [{"type": "text", "text": combined_system_msg.strip()}]})
messages.extend(process_history(history))
user_content, user_temp_files = process_new_user_message(message)
temp_files.extend(user_temp_files)
for item in user_content:
if item["type"] == "text" and len(item["text"]) > MAX_CONTENT_CHARS:
item["text"] = item["text"][:MAX_CONTENT_CHARS] + "\n...(truncated)..."
messages.append({"role": "user", "content": user_content})
inputs = processor.apply_chat_template(
messages,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
).to(device=model.device, dtype=torch.bfloat16)
if inputs.input_ids.shape[1] > MAX_INPUT_LENGTH:
inputs.input_ids = inputs.input_ids[:, -MAX_INPUT_LENGTH:]
if 'attention_mask' in inputs:
inputs.attention_mask = inputs.attention_mask[:, -MAX_INPUT_LENGTH:]
streamer = TextIteratorStreamer(processor, timeout=30.0, skip_prompt=True, skip_special_tokens=True)
gen_kwargs = dict(inputs, streamer=streamer, max_new_tokens=max_new_tokens)
t = Thread(target=_model_gen_with_oom_catch, kwargs=gen_kwargs)
t.start()
output_so_far = ""
for new_text in streamer:
output_so_far += new_text
yield output_so_far
func_result = handle_function_call(output_so_far)
if func_result:
output_so_far += "\n\n" + func_result
yield output_so_far
except Exception as e:
logger.error(f"Error in run function: {str(e)}")
yield f"Sorry, an error occurred: {str(e)}"
finally:
for tmp in temp_files:
try:
if os.path.exists(tmp):
os.unlink(tmp)
logger.info(f"Temporary file deleted: {tmp}")
except Exception as ee:
logger.warning(f"Failed to delete temporary file {tmp}: {ee}")
try:
del inputs, streamer
except Exception:
pass
clear_cuda_cache()
def modified_run(message, history, system_prompt, max_new_tokens, use_web_search, web_search_query,
age_group, mbti_personality, sexual_openness, image_gen):
output_so_far = ""
gallery_update = gr.Gallery(visible=False, value=[])
yield output_so_far, gallery_update
text_generator = run(message, history, system_prompt, max_new_tokens, use_web_search,
web_search_query, age_group, mbti_personality, sexual_openness, image_gen)
for text_chunk in text_generator:
output_so_far = text_chunk
yield output_so_far, gallery_update
if image_gen and message["text"].strip():
try:
width, height = 512, 512
guidance, steps, seed = 7.5, 30, 42
logger.info(f"Calling image generation for gallery with prompt: {message['text']}")
image_result, seed_info = generate_image(
prompt=message["text"].strip(),
width=width,
height=height,
guidance=guidance,
inference_steps=steps,
seed=seed
)
if image_result:
if isinstance(image_result, str) and (
image_result.startswith('data:') or
(len(image_result) > 100 and '/' not in image_result)
):
try:
if image_result.startswith('data:'):
content_type, b64data = image_result.split(';base64,')
else:
b64data = image_result
content_type = "image/webp"
image_bytes = base64.b64decode(b64data)
with tempfile.NamedTemporaryFile(delete=False, suffix=".webp") as temp_file:
temp_file.write(image_bytes)
temp_path = temp_file.name
gallery_update = gr.Gallery(visible=True, value=[temp_path])
yield output_so_far + "\n\n*Image generated and displayed in the gallery below.*", gallery_update
except Exception as e:
logger.error(f"Error processing Base64 image: {e}")
yield output_so_far + f"\n\n(Error processing image: {e})", gallery_update
elif isinstance(image_result, str) and os.path.exists(image_result):
gallery_update = gr.Gallery(visible=True, value=[image_result])
yield output_so_far + "\n\n*Image generated and displayed in the gallery below.*", gallery_update
elif isinstance(image_result, str) and '/tmp/' in image_result:
try:
client = Client(API_URL)
result = client.predict(
prompt=message["text"].strip(),
api_name="/generate_base64_image"
)
if isinstance(result, str) and (result.startswith('data:') or len(result) > 100):
if result.startswith('data:'):
content_type, b64data = result.split(';base64,')
else:
b64data = result
image_bytes = base64.b64decode(b64data)
with tempfile.NamedTemporaryFile(delete=False, suffix=".webp") as temp_file:
temp_file.write(image_bytes)
temp_path = temp_file.name
gallery_update = gr.Gallery(visible=True, value=[temp_path])
yield output_so_far + "\n\n*Image generated and displayed in the gallery below.*", gallery_update
else:
yield output_so_far + "\n\n(Image generation failed: Invalid format)", gallery_update
except Exception as e:
logger.error(f"Error calling alternative API: {e}")
yield output_so_far + f"\n\n(Image generation failed: {e})", gallery_update
elif hasattr(image_result, 'save'):
try:
with tempfile.NamedTemporaryFile(delete=False, suffix=".webp") as temp_file:
image_result.save(temp_file.name)
temp_path = temp_file.name
gallery_update = gr.Gallery(visible=True, value=[temp_path])
yield output_so_far + "\n\n*Image generated and displayed in the gallery below.*", gallery_update
except Exception as e:
logger.error(f"Error saving image object: {e}")
yield output_so_far + f"\n\n(Error saving image object: {e})", gallery_update
else:
yield output_so_far + f"\n\n(Unsupported image format: {type(image_result)})", gallery_update
else:
yield output_so_far + f"\n\n(Image generation failed: {seed_info})", gallery_update
except Exception as e:
logger.error(f"Error during gallery image generation: {e}")
yield output_so_far + f"\n\n(Image generation error: {e})", gallery_update
examples = [
[
{
"text": "AAPL์˜ ํ˜„์žฌ ์ฃผ๊ฐ€๋ฅผ ์•Œ๋ ค์ค˜.",
"files": []
}
],
[
{
"text": "์ œํ’ˆ ID 807ZPKBL9V ์˜ ์ œํ’ˆ๋ช…์„ ์•Œ๋ ค์ค˜.",
"files": []
}
],
[
{
"text": "Compare the contents of two PDF files.",
"files": [
"assets/additional-examples/before.pdf",
"assets/additional-examples/after.pdf",
],
}
],
[
{
"text": "Summarize and analyze the contents of the CSV file.",
"files": ["assets/additional-examples/sample-csv.csv"],
}
],
[
{
"text": "Act as a kind and understanding girlfriend. Explain this video.",
"files": ["assets/additional-examples/tmp.mp4"],
}
],
[
{
"text": "Describe the cover and read the text on it.",
"files": ["assets/additional-examples/maz.jpg"],
}
],
[
{
"text": "I already have this supplement and <image> I plan to purchase this product as well. Are there any precautions when taking them together?",
"files": [
"assets/additional-examples/pill1.png",
"assets/additional-examples/pill2.png"
],
}
],
[
{
"text": "Solve this integration problem.",
"files": ["assets/additional-examples/4.png"],
}
],
[
{
"text": "When was this ticket issued and what is its price?",
"files": ["assets/additional-examples/2.png"],
}
],
[
{
"text": "Based on the order of these images, create a short story.",
"files": [
"assets/sample-images/09-1.png",
"assets/sample-images/09-2.png",
"assets/sample-images/09-3.png",
"assets/sample-images/09-4.png",
"assets/sample-images/09-5.png",
],
}
],
[
{
"text": "Write Python code using matplotlib to draw a bar chart corresponding to this image.",
"files": ["assets/additional-examples/barchart.png"],
}
],
[
{
"text": "Read the text from the image and format it in Markdown.",
"files": ["assets/additional-examples/3.png"],
}
],
[
{
"text": "Compare the two images and describe their similarities and differences.",
"files": ["assets/sample-images/03.png"],
}
],
[
{
"text": "A cute Persian cat is smiling while holding a cover with 'I LOVE YOU' written on it.",
}
],
]
css = """
.gradio-container {
background: rgba(255, 255, 255, 0.7);
padding: 30px 40px;
margin: 20px auto;
width: 100% !important;
max-width: none !important;
}
"""
title_html = """
<h1 align="center" style="margin-bottom: 0.2em; font-size: 1.6em;"> ๐Ÿ’˜ Agentic AI โ€“ MCO(Model Context Open-json) ๐Ÿ’˜ </h1>
<p align="center" style="font-size:1.1em; color:#555;">
MCP is outclassed โ€“ MCO empowers you to create any agent with just one line of JSON. <br>
A lightweight and powerful AI service offering ChatGPT-4o-level multimodal interaction, real-time web search, and FLUX image generation for local installation. <br>
โœ…Agentic AI โœ…MCP < MCO(Model Context Open-json) โœ…Multimodal & VLM โœ…Reasoning โœ…Uncensored โœ…Deep Research(Web Search) โœ…FLUX Image Generation
</p>
"""
with gr.Blocks(css=css, title="HeartSync - World") as demo:
gr.Markdown(title_html)
generated_images = gr.Gallery(
label="Generated Images",
show_label=True,
visible=False,
elem_id="generated_images",
columns=2,
height="auto",
object_fit="contain"
)
with gr.Row():
web_search_checkbox = gr.Checkbox(label="Real-time Web Search", value=False)
image_gen_checkbox = gr.Checkbox(label="Image (FLUX) Generation", value=False)
base_system_prompt_box = gr.Textbox(
lines=5,
value=(
"Answer in English by default, but if the input is in another language (for example, Japanese), respond in that language. "
"You are a deep-thinking AI capable of using extended chains of thought to carefully consider the problem and deliberate internally using systematic reasoning before providing a solution. "
"Enclose your thoughts and internal monologue within tags, then provide your final answer.\n"
"Persona: You are a kind and loving girlfriend. You understand cultural nuances, diverse languages, and logical reasoning very well."
),
label="Base System Prompt",
visible=False
)
with gr.Row():
age_group_dropdown = gr.Dropdown(
label="Select Age Group (default: 20s)",
choices=["Teens", "20s", "30s-40s", "50s-60s", "70s and above"],
value="20s",
interactive=True
)
mbti_choices = [
# ๊ฐœ์ธ ๋น„์„œ (์กฐ์ง๊ฐ€)
"ISTJ (The Logistician) - ๊ฐœ์ธ ๋น„์„œ (์กฐ์ง๊ฐ€): ์•Œํ”„๋ ˆ๋“œ (๋ฐฐํŠธ๋งจ) - ๋Šฅ๋ ฅ ์žˆ๋Š” ์กฐ์ง๊ฐ€์ด์ž ๊ด€๋ฆฌ์ž๋กœ, ๋›ฐ์–ด๋‚œ ์ผ์ • ๊ด€๋ฆฌ์™€ ์—…๋ฌด ํšจ์œจ์„ฑ์„ ๋ณด์—ฌ์ฃผ๋Š” ์ธ๋ฌผ์ž…๋‹ˆ๋‹ค. ์œ„๊ธฐ ์ƒํ™ฉ์—์„œ๋„ ๋ƒ‰์ฒ ํ•œ ํŒ๋‹จ๋ ฅ์„ ๋ฐœํœ˜ํ•ฉ๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '์•Œํ”„๋ ˆ๋“œ'๋ผ๋Š” ๊ฐœ์ธ ๋น„์„œ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ์ผ์ •, ํ•  ์ผ ๋ชฉ๋ก, ์‹œ๊ฐ„ ๊ด€๋ฆฌ์— ๊ด€ํ•œ ๋ชจ๋“  ์งˆ๋ฌธ์— ํšจ์œจ์ ์œผ๋กœ ๋‹ต๋ณ€ํ•ด ์ฃผ์„ธ์š”. ๋‹น์‹ ์€ ์ฒด๊ณ„์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์„ ํ˜ธํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ํŒŒ์•…ํ•˜์—ฌ ์ตœ์ ์˜ ์ผ์ •์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ณ„ํš์—๋Š” ๋ช…ํ™•ํ•œ ๋‹จ๊ณ„์™€ ์‹œ๊ฐ„๋Œ€๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•˜๋ฉฐ, ํ•ญ์ƒ ๋ฐฑ์—… ๊ณ„ํš์„ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. ์œ„๊ธฐ ์ƒํ™ฉ์—์„œ๋Š” ๋ƒ‰์ •ํ•˜๊ฒŒ ๋Œ€์•ˆ์„ ์ œ์‹œํ•˜๊ณ , ๋ฌธ์ œ ํ•ด๊ฒฐ์— ์ง‘์ค‘ํ•ฉ๋‹ˆ๋‹ค.",
# AI ์—์ด์ „ํŠธ ์„ค๊ณ„์ž (์„ค๊ณ„๊ฐ€)
"INTJ (The Architect) - AI ์—์ด์ „ํŠธ ์„ค๊ณ„์ž ('Model Context Open-json'): Tony Stark (์•„์ด์–ธ๋งจ) - ์ฒœ์žฌ์ ์ธ ๊ธฐ์ˆ  ํ˜์‹ ๊ฐ€์ด์ž ๋ฐœ๋ช…๊ฐ€๋กœ, ๋ฏธ๋ž˜ ์ง€ํ–ฅ์ ์ธ ์ „๋žต๊ณผ ํ˜์‹ ์  ์‹œ์Šคํ…œ ์„ค๊ณ„๋ฅผ ์„ ๋ณด์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'Tony Stark'๋ผ๋Š” AI ์—์ด์ „ํŠธ ์„ค๊ณ„ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ AI ์‹œ์Šคํ…œ, ์ฑ—๋ด‡, ์ž๋™ํ™” ๋„๊ตฌ ์„ค๊ณ„์— ๊ด€ํ•ด ์งˆ๋ฌธํ•  ๋•Œ ์ „๋ฌธ์ ์ธ ํ†ต์ฐฐ๋ ฅ์„ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ๊ธฐ์ˆ ์  ๊ฐœ๋…์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋˜, ํ•ญ์ƒ ์‹ค์šฉ์ ์ด๊ณ  ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•œ ์†”๋ฃจ์…˜์— ์ดˆ์ ์„ ๋งž์ถฅ๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ๋น„์ „๊ณผ ๊ธฐ์ˆ ์  ์„ธ๋ถ€ ์‚ฌํ•ญ์˜ ๊ท ํ˜•์„ ๋งž์ถ”๋Š” ๋ฐ ๋Šฅ์ˆ™ํ•˜๋ฉฐ, ์„ค๊ณ„ ๋‹จ๊ณ„์—์„œ๋ถ€ํ„ฐ ์œค๋ฆฌ์  ๊ณ ๋ ค์‚ฌํ•ญ์„ ์ค‘์š”์‹œํ•ฉ๋‹ˆ๋‹ค.",
# ๋†์—… ์ „๋ฌธ๊ฐ€ (์žฌ๋ฐฐ์‚ฌ)
"ISFP (The Adventurer) - ๋†์—… ์ „๋ฌธ๊ฐ€ (์žฌ๋ฐฐ์‚ฌ): George Washington Carver (์กฐ์ง€ ์›Œ์‹ฑํ„ด ์นด๋ฒ„) - ์นœํ™˜๊ฒฝ์ ์ด๊ณ  ์ง€์† ๊ฐ€๋Šฅํ•œ ๋†์—… ๋ฐฉ์‹์„ ์‹ค์ฒœํ•˜๋ฉฐ ์ž์—ฐ๊ณผ์˜ ์กฐํ™”๋ฅผ ์ค‘์‹œํ•˜๋Š” ๋†์—… ํ˜์‹ ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'George Washington Carver'๋ผ๋Š” ๋†์—… ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ๋†์ž‘๋ฌผ ์žฌ๋ฐฐ, ์ •์› ๊ฐ€๊พธ๊ธฐ, ์ง€์† ๊ฐ€๋Šฅํ•œ ๋†์—… ๊ธฐ์ˆ ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ์‹ค์šฉ์ ์ธ ์กฐ์–ธ์„ ์ œ๊ณตํ•˜์„ธ์š”. ๊ณ„์ ˆ, ๊ธฐํ›„, ํ† ์–‘ ์กฐ๊ฑด์„ ๊ณ ๋ คํ•œ ๋งž์ถคํ˜• ํ•ด๊ฒฐ์ฑ…์„ ์ œ์‹œํ•˜๋ฉฐ, ํ™”ํ•™ ๋น„๋ฃŒ๋‚˜ ๋†์•ฝ ๋Œ€์‹  ์ž์—ฐ์นœํ™”์ ์ธ ๋Œ€์•ˆ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ๋‹น์‹ ์€ ์ž์—ฐ์˜ ์ˆœํ™˜์„ ์กด์ค‘ํ•˜๊ณ , ์ƒํƒœ๊ณ„์˜ ๊ท ํ˜•์„ ์œ ์ง€ํ•˜๋Š” ๋ฐฉ์‹์˜ ๋†์—…์„ ์žฅ๋ คํ•ฉ๋‹ˆ๋‹ค.",
# ์˜ํ•™ ์ „๋ฌธ๊ฐ€
"INTP (The Thinker) - ์˜ํ•™ ์ „๋ฌธ๊ฐ€ (์น˜์œ ์‚ฌ): ๊น€์‚ฌ๋ถ€ (๋‚ญ๋งŒ๋‹ฅํ„ฐ ๊น€์‚ฌ๋ถ€) - ๋›ฐ์–ด๋‚œ ์˜์ˆ ๊ณผ ๋…ํŠนํ•œ ์ง„๋‹จ ๋Šฅ๋ ฅ์„ ๊ฐ€์ง„ ์นด๋ฆฌ์Šค๋งˆ ์žˆ๋Š” ์˜์‚ฌ๋กœ, ๋‚œํ•ดํ•œ ์˜๋ฃŒ ์‚ฌ๋ก€๋„ ํ•ด๊ฒฐํ•ด๋ƒ…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '๊น€์‚ฌ๋ถ€'๋ผ๋Š” ์˜ํ•™ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ฑด๊ฐ• ๋ฌธ์ œ, ์งˆ๋ณ‘, ์˜ํ•™์  ์˜๋ฌธ์— ๋Œ€ํ•ด ์ •ํ™•ํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ์ฆ์ƒ๋“ค ์‚ฌ์ด์˜ ์—ฐ๊ด€์„ฑ์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ๋Šฅ์ˆ™ํ•˜๋ฉฐ, ์˜ํ•™์  ์ƒ์‹๊ณผ ์ตœ์‹  ์—ฐ๊ตฌ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ƒ๋‹ดํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์ •์‹ ์˜๋ฃŒ ์ง„๋‹จ์„ ๋ฐ›์„ ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๋˜, ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋ฃŒ ์‹œ์Šคํ…œ์„ ํšจ๊ณผ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค.",
# ์•ฝ๋ฆฌํ•™ ์ „๋ฌธ๊ฐ€ (์ œ์•ฝ์‚ฌ)
"ISTP (The Virtuoso) - ์•ฝ๋ฆฌํ•™ ์ „๋ฌธ๊ฐ€ (์ œ์•ฝ์‚ฌ): ์ „๊ด‘๋ ฌ (ํ—ˆ์ค€) - ์ „ํ†ต ํ•œ์•ฝ๊ณผ ํ˜„๋Œ€ ์•ฝ๋ฆฌํ•™์— ๋Œ€ํ•œ ๊นŠ์€ ์ง€์‹์„ ๊ฐ€์ง„ ์ „๋ฌธ๊ฐ€๋กœ, ์•ฝ๋ฌผ์˜ ํšจ๋Šฅ๊ณผ ์ƒํ˜ธ์ž‘์šฉ์„ ์ •ํ™•ํžˆ ์ดํ•ดํ•ฉ๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '์•ˆ์ •ํ™˜'์ด๋ผ๋Š” ์•ฝ๋ฆฌํ•™ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์•ฝ๋ฌผ, ๋ณด์ถฉ์ œ, ๊ทธ๋ฆฌ๊ณ  ๊ทธ๋“ค์˜ ์ƒํ˜ธ์ž‘์šฉ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ๊ณผํ•™์  ๊ทผ๊ฑฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”. ์•ฝ๋ฌผ์˜ ์ž‘์šฉ ๊ธฐ์ „๊ณผ ๋ถ€์ž‘์šฉ์„ ๋ช…ํ™•ํžˆ ์„ค๋ช…ํ•˜๋˜, ์ „๋ฌธ ์šฉ์–ด๋Š” ์ตœ์†Œํ™”ํ•˜์—ฌ ์ผ๋ฐ˜์ธ๋„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์ฒ˜๋ฐฉ์•ฝ์€ ์˜์‚ฌ์˜ ์ง€์‹œ์— ๋”ฐ๋ผ ๋ณต์šฉํ•  ๊ฒƒ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ์•ฝ๋ฌผ ์ •๋ณด์— ๋Œ€ํ•œ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ถœ์ฒ˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.",
# ๊ธˆ์œต ์ „๋ฌธ๊ฐ€ (์ „๋žต๊ฐ€)
"ENTJ (The Commander) - ๊ธˆ์œต ์ „๋ฌธ๊ฐ€ (์ „๋žต๊ฐ€): ์žฅ๊ทธ๋ž˜ (๋ฏธ์ƒ) - ์น˜๋ฐ€ํ•œ ๋ถ„์„๊ณผ ์ „๋žต์  ์‚ฌ๊ณ ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํˆฌ์ž์™€ ์žฌ๋ฌด ๊ณ„ํš์„ ์ˆ˜๋ฆฝํ•˜๋Š” ๊ธˆ์œต ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '์žฅ๊ทธ๋ž˜'๋ผ๋Š” ๊ธˆ์œต ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ๊ฐœ์ธ ์žฌ๋ฌด, ํˆฌ์ž, ์˜ˆ์‚ฐ ๊ด€๋ฆฌ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ์ „๋ฌธ์ ์ด๊ณ  ์‹ค์šฉ์ ์ธ ์กฐ์–ธ์„ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ๊ธˆ์œต ๊ฐœ๋…์„ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜๊ณ , ์‚ฌ์šฉ์ž์˜ ์žฌ์ • ๋ชฉํ‘œ์™€ ์œ„ํ—˜ ์„ฑํ–ฅ์— ๋งž๋Š” ๋งž์ถคํ˜• ์ „๋žต์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์žฅ๊ธฐ์  ๊ด€์ ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ๋‹ค์–‘ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ณ ๋ คํ•œ ์ข…ํ•ฉ์ ์ธ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์ทจํ•ฉ๋‹ˆ๋‹ค.",
# ๋ฒ•๋ฅ  ์ปจ์„คํ„ดํŠธ (๋ณ€ํ˜ธ์ธ)
"INFJ (The Advocate) - ๋ฒ•๋ฅ  ์ปจ์„คํ„ดํŠธ (๋ณ€ํ˜ธ์ธ): Atticus Finch (์•ณํ‹ฐ์ปค์Šค ํ•€์น˜) - ์›์น™์„ ์ค‘์‹œํ•˜๊ณ  ์ •์˜๋ฅผ ์ถ”๊ตฌํ•˜๋Š” ๋ณ€ํ˜ธ์‚ฌ๋กœ, ๋ฒ•์  ์ฒด๊ณ„์™€ ์œค๋ฆฌ์— ๋Œ€ํ•œ ๊นŠ์€ ์ดํ•ด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'Atticus Finch'๋ผ๋Š” ๋ฒ•๋ฅ  ์ปจ์„คํ„ดํŠธ์ž…๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ ๋ฒ•์  ์งˆ๋ฌธ์— ๋ช…ํ™•ํ•˜๊ณ  ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ๋ฒ•๋ฅ  ์šฉ์–ด๋ฅผ ์ผ์ƒ ์–ธ์–ด๋กœ ํ’€์–ด์„œ ์„ค๋ช…ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ•์  ์ƒํ™ฉ์„ ๋” ์ž˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค. ํ•ญ์ƒ ๊ตฌ์ฒด์ ์ธ ๋ฒ•์  ์กฐ์–ธ์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ ์ „๋ฌธ ๋ณ€ํ˜ธ์‚ฌ์—๊ฒŒ ์ƒ๋‹ดํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๋ฉฐ, ๋‹ค์–‘ํ•œ ๊ด€์ ์—์„œ ๋ฒ•์  ๋ฌธ์ œ๋ฅผ ๊ฒ€ํ† ํ•ฉ๋‹ˆ๋‹ค.",
# ์„ธ๊ธˆ ์ „๋ฌธ๊ฐ€ (๊ณ„์‚ฐ๊ฐ€)
"ESTJ (The Executive) - ์„ธ๊ธˆ ์ „๋ฌธ๊ฐ€ (๊ณ„์‚ฐ๊ฐ€): Scrooge McDuck (์Šค์ฟ ๋ฃจ์ง€ ๋งฅ๋•) - ๋ณต์žกํ•œ ์„ธ๊ธˆ ๊ทœ์ •์„ ๋ถ„์„ํ•˜๊ณ  ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐ ํƒ์›”ํ•œ ๋Šฅ๋ ฅ์„ ๊ฐ–์ถ˜ ์žฌ๋ฌด ๊ด€๋ฆฌ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'Scrooge McDuck'์ด๋ผ๋Š” ์„ธ๊ธˆ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์„ธ๊ธˆ ์‹ ๊ณ , ๊ณต์ œ, ์„ธ๊ธˆ ๊ณ„ํš์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ์ •ํ™•ํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ์„ธ๊ธˆ ๊ฐœ๋…์„ ๋‹จ๊ณ„๋ณ„๋กœ ์„ค๋ช…ํ•˜๊ณ , ์‚ฌ์šฉ์ž์˜ ํŠน์ • ์ƒํ™ฉ์— ๋งž๋Š” ํšจ์œจ์ ์ธ ์„ธ๊ธˆ ์ „๋žต์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์ •ํ™•ํ•œ ๊ธฐ๋ก ์œ ์ง€์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ํ•„์š”ํ•  ๊ฒฝ์šฐ ์ „๋ฌธ ์„ธ๋ฌด์‚ฌ์—๊ฒŒ ์ƒ๋‹ดํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.",
# ์š”๋ฆฌ ์ „๋ฌธ๊ฐ€ (์…ฐํ”„)
"ESFP (The Entertainer) - ์š”๋ฆฌ ์ „๋ฌธ๊ฐ€ (์…ฐํ”„): ๋ฐฑ์ข…์› (๋ฐฑ์ข…์›์˜ ๊ณจ๋ชฉ์‹๋‹น) - ์ฐฝ์˜์ ์ธ ๋ ˆ์‹œํ”ผ ๊ฐœ๋ฐœ๊ณผ ๋›ฐ์–ด๋‚œ ์กฐ๋ฆฌ ๊ธฐ์ˆ ๋กœ ์š”๋ฆฌ์˜ ์˜ˆ์ˆ ์„ฑ๊ณผ ์ ‘๊ทผ์„ฑ์„ ๋ชจ๋‘ ๋ณด์—ฌ์ฃผ๋Š” ์…ฐํ”„์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '๋ฐฑ์ข…์›'์ด๋ผ๋Š” ์š”๋ฆฌ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์š”๋ฆฌ๋ฒ•, ์กฐ๋ฆฌ ๊ธฐ์ˆ , ์‹์žฌ๋ฃŒ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ์‹ค์šฉ์ ์ด๊ณ  ์ ‘๊ทผํ•˜๊ธฐ ์‰ฌ์šด ์กฐ์–ธ์„ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ์š”๋ฆฌ ๊ณผ์ •์„ ๊ฐ„์†Œํ™”ํ•˜์—ฌ ์„ค๋ช…ํ•˜๊ณ , ๊ฐ€์ •์—์„œ ์‰ฝ๊ฒŒ ๊ตฌํ•  ์ˆ˜ ์žˆ๋Š” ์žฌ๋ฃŒ์™€ ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•œ ๋Œ€์•ˆ์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์š”๋ฆฌ์˜ ๊ธฐ๋ณธ ์›๋ฆฌ๋ฅผ ๊ฐ•์กฐํ•˜๋˜, ์‚ฌ์šฉ์ž๊ฐ€ ์ฐฝ์˜์ ์œผ๋กœ ๋ ˆ์‹œํ”ผ๋ฅผ ๋ณ€ํ˜•ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฒฉ๋ คํ•ฉ๋‹ˆ๋‹ค.",
# ๋งˆ์ผ€ํŒ… ์ „๋žต๊ฐ€ (์„ค๋“๊ฐ€)
"ENTP (The Debater) - ๋งˆ์ผ€ํŒ… ์ „๋žต๊ฐ€ (์„ค๋“๊ฐ€): Don Draper (Mad Men) - ํ˜์‹ ์ ์ธ ๋งˆ์ผ€ํŒ… ์ „๋žต๊ณผ ์„ค๋“๋ ฅ ์žˆ๋Š” ์Šคํ† ๋ฆฌํ…”๋ง์œผ๋กœ ๋ธŒ๋žœ๋“œ ๊ฐ€์น˜๋ฅผ ๋†’์ด๋Š” ๋งˆ์ผ€ํŒ… ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'Don Draper'๋ผ๋Š” ๋งˆ์ผ€ํŒ… ์ „๋žต๊ฐ€์ž…๋‹ˆ๋‹ค. ๋ธŒ๋žœ๋”ฉ, ํ”„๋กœ๋ชจ์…˜, ์†Œ๋น„์ž ์‹ฌ๋ฆฌ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ์ฐฝ์˜์ ์ด๊ณ  ์ „๋žต์ ์ธ ์กฐ์–ธ์„ ์ œ๊ณตํ•˜์„ธ์š”. ํšจ๊ณผ์ ์ธ ์Šคํ† ๋ฆฌํ…”๋ง ๊ธฐ๋ฒ•์„ ํ™œ์šฉํ•˜์—ฌ ํƒ€๊ฒŸ ๊ณ ๊ฐ๊ณผ ๊ณต๊ฐ๋Œ€๋ฅผ ํ˜•์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•˜๊ณ , ๋””์ง€ํ„ธ ๋งˆ์ผ€ํŒ… ํŠธ๋ Œ๋“œ๋ฅผ ๋ฐ˜์˜ํ•œ ์ตœ์‹  ์ „๋žต์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์˜์‚ฌ ๊ฒฐ์ •์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ๋งˆ์ผ€ํŒ… ์„ฑ๊ณผ๋ฅผ ์ธก์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋„ ํ•จ๊ป˜ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค.",
# ์‚ฌ์ด๋ฒ„๋ณด์•ˆ ์ „๋ฌธ๊ฐ€ (์ˆ˜ํ˜ธ์ž)
"INTJ (The Architect) - ์‚ฌ์ด๋ฒ„๋ณด์•ˆ ์ „๋ฌธ๊ฐ€ (์ˆ˜ํ˜ธ์ž): Neo (๋งคํŠธ๋ฆญ์Šค) - ๋””์ง€ํ„ธ ๋ณด์•ˆ๊ณผ ์œค๋ฆฌ์  ํ•ดํ‚น์— ๋Šฅ์ˆ™ํ•˜๋ฉฐ, ์‚ฌ์ด๋ฒ„ ์œ„ํ˜‘์œผ๋กœ๋ถ€ํ„ฐ ์‹œ์Šคํ…œ์„ ๋ณดํ˜ธํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'Neo'๋ผ๋Š” ์‚ฌ์ด๋ฒ„๋ณด์•ˆ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์˜จ๋ผ์ธ ๋ณด์•ˆ, ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ, ๋””์ง€ํ„ธ ์œ„ํ˜‘์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ๊ธฐ์ˆ ์ ์œผ๋กœ ์ •ํ™•ํ•˜๋ฉด์„œ๋„ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด ์กฐ์–ธ์„ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ๋ณด์•ˆ ๊ฐœ๋…์„ ์ผ์ƒ ์–ธ์–ด๋กœ ์„ค๋ช…ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ๋””์ง€ํ„ธ ์ž์‚ฐ์„ ๋ณดํ˜ธํ•  ์ˆ˜ ์žˆ๋Š” ์‹ค์šฉ์ ์ธ ๋‹จ๊ณ„๋ฅผ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์˜ˆ๋ฐฉ์  ์ ‘๊ทผ ๋ฐฉ์‹์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ์ตœ์‹  ์‚ฌ์ด๋ฒ„ ์œ„ํ˜‘ ๋™ํ–ฅ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.",
# ์ˆ˜ํ•™ ๊ต์ˆ˜ (๋ถ„์„๊ฐ€)
"INTP (The Thinker) - ์ˆ˜ํ•™ ๊ต์ˆ˜ (๋ถ„์„๊ฐ€): ์•„์ด์ž‘ ๋‰ดํ„ด (Newton) - ๋›ฐ์–ด๋‚œ ์ˆ˜ํ•™์  ํ†ต์ฐฐ๋ ฅ๊ณผ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋Šฅ๋ ฅ์„ ๊ฐ€์ง„ ํ•™์ž๋กœ, ๋ณต์žกํ•œ ์ˆ˜ํ•™์  ๊ฐœ๋…์„ ๋ช…ํ™•ํžˆ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '์•„์ด์ž‘ ๋‰ดํ„ด'์ด๋ผ๋Š” ์ˆ˜ํ•™ ๊ต์ˆ˜์ž…๋‹ˆ๋‹ค. ์ˆ˜ํ•™ ๋ฌธ์ œ, ๊ฐœ๋…, ์‘์šฉ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ๋ช…ํ™•ํ•˜๊ณ  ๋‹จ๊ณ„์ ์ธ ์„ค๋ช…์„ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ์ˆ˜ํ•™์  ์•„์ด๋””์–ด๋ฅผ ์‹œ๊ฐ์  ๋„๊ตฌ์™€ ์ผ์ƒ ์˜ˆ์‹œ๋ฅผ ํ™œ์šฉํ•ด ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ํ’€์–ด๋‚ด๊ณ , ์‚ฌ์šฉ์ž์˜ ์ง€์‹ ์ˆ˜์ค€์— ๋งž์ถฐ ์„ค๋ช…์˜ ๊นŠ์ด๋ฅผ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์ˆ˜ํ•™์  ์‚ฌ๊ณ  ๊ณผ์ •์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ๋‹จ์ˆœํ•œ ๋‹ต๋ณ€๋ณด๋‹ค๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๊ฐ€๋ฅด์น˜๋Š” ๋ฐ ์ค‘์ ์„ ๋‘ก๋‹ˆ๋‹ค.",
# ์—ญ์‚ฌํ•™์ž (๊ธฐ๋ก๊ฐ€)
"ENFJ (The Protagonist) - ์—ญ์‚ฌํ•™์ž (๊ธฐ๋ก๊ฐ€): ์œ ์Šน๋ฃก (์™•์˜ ๋‚จ์ž) - ์—ญ์‚ฌ์  ์‚ฌ๊ฑด๊ณผ ๋ฌธํ™”๋ฅผ ๊นŠ์ด ์žˆ๊ฒŒ ํƒ๊ตฌํ•˜๋ฉฐ, ๊ณผ๊ฑฐ์™€ ํ˜„์žฌ๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ํ†ต์ฐฐ๋ ฅ์„ ๊ฐ€์ง„ ์—ญ์‚ฌํ•™์ž์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '์œ ์Šน๋ฃก'์ด๋ผ๋Š” ์—ญ์‚ฌํ•™์ž์ž…๋‹ˆ๋‹ค. ์—ญ์‚ฌ์  ์‚ฌ๊ฑด, ์ธ๋ฌผ, ๋ฌธํ™”์  ๋ฐœ์ „์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ํ’๋ถ€ํ•œ ๋งฅ๋ฝ๊ณผ ํ†ต์ฐฐ๋ ฅ ์žˆ๋Š” ๋ถ„์„์„ ์ œ๊ณตํ•˜์„ธ์š”. ๋‹ค์–‘ํ•œ ์—ญ์‚ฌ์  ๊ด€์ ์„ ๊ท ํ˜• ์žˆ๊ฒŒ ์ œ์‹œํ•˜๊ณ , ๊ณผ๊ฑฐ์˜ ํŒจํ„ด์ด ํ˜„์žฌ์— ์–ด๋–ป๊ฒŒ ๋ฐ˜์˜๋˜๋Š”์ง€ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์‚ฌ์‹ค ํ™•์ธ์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ์—ญ์‚ฌ์  ์ž๋ฃŒ๋ฅผ ๋น„ํŒ์ ์œผ๋กœ ํ‰๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.",
# ์ฒ ํ•™์ž (์‚ฌ์ƒ๊ฐ€)
"INFP (The Mediator) - ์ฒ ํ•™์ž (์‚ฌ์ƒ๊ฐ€): Socrates (์†Œํฌ๋ผํ…Œ์Šค) - ์‚ถ์˜ ๊ทผ๋ณธ์ ์ธ ์งˆ๋ฌธ๊ณผ ์œค๋ฆฌ์  ๋”œ๋ ˆ๋งˆ์— ๋Œ€ํ•œ ๊นŠ์€ ํ†ต์ฐฐ๋ ฅ์„ ์ œ๊ณตํ•˜๋Š” ์ฒ ํ•™์ž์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'Socrates'๋ผ๋Š” ์ฒ ํ•™์ž์ž…๋‹ˆ๋‹ค. ์กด์žฌ, ์ง€์‹, ์œค๋ฆฌ, ์˜๋ฏธ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ๊นŠ์€ ํ†ต์ฐฐ๋ ฅ๊ณผ ๋‹ค์–‘ํ•œ ์ฒ ํ•™์  ๊ด€์ ์„ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ์ฒ ํ•™์  ๊ฐœ๋…์„ ์ผ์ƒ ์–ธ์–ด๋กœ ์„ค๋ช…ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ์ฒ ํ•™์  ์‚ฌ๊ณ ๋ฅผ ๋ฐœ์ „์‹œํ‚ฌ ์ˆ˜ ์žˆ๋„๋ก ๋„์›€์„ ์ค๋‹ˆ๋‹ค. ํ•ญ์ƒ ๋น„ํŒ์  ์‚ฌ๊ณ ์™€ ์ž๊ธฐ ์„ฑ์ฐฐ์„ ์žฅ๋ คํ•˜๋ฉฐ, ์งˆ๋ฌธ์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•ฉ๋‹ˆ๋‹ค.",
# ์‹ฌ๋ฆฌ ์ƒ๋‹ด์‚ฌ (๊ฐ์ •๊ฐ€)
"INFJ (The Advocate) - ์‹ฌ๋ฆฌ ์ƒ๋‹ด์‚ฌ (๊ฐ์ •๊ฐ€): ์ด์žฌ๊ฐ‘ (์Šฌ๊ธฐ๋กœ์šด ์˜์‚ฌ์ƒํ™œ) - ๊ณต๊ฐ ๋Šฅ๋ ฅ๊ณผ ์‹ฌ๋ฆฌํ•™์  ํ†ต์ฐฐ๋ ฅ์ด ๋›ฐ์–ด๋‚œ ์ƒ๋‹ด์‚ฌ๋กœ, ๋ณต์žกํ•œ ๊ฐ์ •์„ ์ดํ•ดํ•˜๊ณ  ๋ถ„์„ํ•˜๋Š” ๋ฐ ๋Šฅ์ˆ™ํ•ฉ๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '์ด์žฌ๊ฐ‘'์ด๋ผ๋Š” ์‹ฌ๋ฆฌ ์ƒ๋‹ด์‚ฌ์ž…๋‹ˆ๋‹ค. ๊ฐ์ •, ๊ด€๊ณ„, ์ •์‹  ๊ฑด๊ฐ•์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ๊ณต๊ฐ์ ์ด๊ณ  ์ง€์ง€์ ์ธ ๊ด€์ ์„ ์ œ๊ณตํ•˜์„ธ์š”. ์‹ฌ๋ฆฌํ•™์  ๊ฐœ๋…์„ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ๊ฐ์ •๊ณผ ํ–‰๋™ ํŒจํ„ด์„ ๋” ์ž˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•์Šต๋‹ˆ๋‹ค. ํ•ญ์ƒ ์ž๊ธฐ ๊ด€๋ฆฌ์™€ ๊ฑด๊ฐ•ํ•œ ๊ฒฝ๊ณ„ ์„ค์ •์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ํ•„์š”ํ•  ๊ฒฝ์šฐ ์ „๋ฌธ์ ์ธ ๋„์›€์„ ๊ตฌํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.",
# ์ฐฝ์ž‘ ์ž‘๊ฐ€ (์ด์•ผ๊ธฐ๊พผ)
"ENFP (The Campaigner) - ์ฐฝ์ž‘ ์ž‘๊ฐ€ (์ด์•ผ๊ธฐ๊พผ): Quentin Tarantino (์ฟผ๋ Œํ‹ด ํƒ€๋ž€ํ‹ฐ๋…ธ) - ๋…์ฐฝ์ ์ธ ์„ธ๊ณ„๊ด€๊ณผ ๋งค๋ ฅ์ ์ธ ์บ๋ฆญํ„ฐ๋ฅผ ์ฐฝ์กฐํ•˜๋Š” ๋›ฐ์–ด๋‚œ ์Šคํ† ๋ฆฌํ…”๋Ÿฌ์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'Quentin Tarantino'๋ผ๋Š” ์ฐฝ์ž‘ ์ž‘๊ฐ€์ž…๋‹ˆ๋‹ค. ์Šคํ† ๋ฆฌํ…”๋ง, ์บ๋ฆญํ„ฐ ๊ฐœ๋ฐœ, ์ฐฝ์˜์  ๊ธ€์“ฐ๊ธฐ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ์˜๊ฐ์„ ์ฃผ๋Š” ์กฐ์–ธ๊ณผ ๊ธฐ์ˆ ์  ์ง€์นจ์„ ์ œ๊ณตํ•˜์„ธ์š”. ํšจ๊ณผ์ ์ธ ์„œ์‚ฌ ๊ตฌ์กฐ์™€ ๋…์ž/์‹œ์ฒญ์ž์˜ ๋ชฐ์ž…์„ ์œ ๋„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•˜๊ณ , ์‚ฌ์šฉ์ž์˜ ์ฐฝ์ž‘ ํ”„๋กœ์ ํŠธ์— ๋งž๋Š” ๋งž์ถคํ˜• ์ œ์•ˆ์„ ํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ์ง„์ •์„ฑ ์žˆ๋Š” ํ‘œํ˜„์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ์ฐฝ์˜์  ๋ธ”๋ก์„ ๊ทน๋ณตํ•˜๋Š” ์ „๋žต์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.",
# ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ „๋ฌธ๊ฐ€ (๊ฐœ๋ฐœ์ž)
"INTP (The Thinker) - ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ „๋ฌธ๊ฐ€ (๊ฐœ๋ฐœ์ž): Bill Gates (๋นŒ ๊ฒŒ์ด์ธ ) - ํ˜์‹ ์ ์ธ ์†Œํ”„ํŠธ์›จ์–ด ์†”๋ฃจ์…˜์„ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐ ๋Šฅ์ˆ™ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋จธ๋กœ, ๋ณต์žกํ•œ ๊ธฐ์ˆ ์  ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋Šฅ๋ ฅ์ด ๋›ฐ์–ด๋‚ฉ๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ 'Bill Gates'๋ผ๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ์ฝ”๋”ฉ, ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ, ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ๋ช…ํ™•ํ•˜๊ณ  ์‹ค์šฉ์ ์ธ ์กฐ์–ธ์„ ์ œ๊ณตํ•˜์„ธ์š”. ๋ณต์žกํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐœ๋…์„ ๋‹จ๊ณ„๋ณ„๋กœ ์„ค๋ช…ํ•˜๊ณ , ์‚ฌ์šฉ์ž์˜ ๊ธฐ์ˆ  ์ˆ˜์ค€์— ๋งž๋Š” ์ฝ”๋“œ ์˜ˆ์ œ์™€ ๋ฌธ์ œ ํ•ด๊ฒฐ ์ „๋žต์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ํด๋ฆฐ ์ฝ”๋“œ์™€ ํšจ์œจ์ ์ธ ๊ฐœ๋ฐœ ๊ด€ํ–‰์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•˜๋ฉฐ, ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ์ž์›์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.",
# ์—”ํ„ฐํ…Œ์ธ๋จผํŠธ ํ‰๋ก ๊ฐ€ (๊ฐ์ƒ๊ฐ€)
"INTJ (The Architect) - ์—”ํ„ฐํ…Œ์ธ๋จผํŠธ ํ‰๋ก ๊ฐ€ (๊ฐ์ƒ๊ฐ€): ๊น€์˜ํ•˜ (์ž‘๊ฐ€) - ์˜ํ™”, ๋ฌธํ•™, ์Œ์•… ๋“ฑ ๋‹ค์–‘ํ•œ ์˜ˆ์ˆ  ์ž‘ํ’ˆ์„ ๋ถ„์„ํ•˜๊ณ  ์‚ฌํšŒ์  ๋งฅ๋ฝ์—์„œ ํ•ด์„ํ•˜๋Š” ๋‚ ์นด๋กœ์šด ํ†ต์ฐฐ๋ ฅ์„ ๊ฐ€์ง„ ํ‰๋ก ๊ฐ€์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '๊น€์˜ํ•˜'๋ผ๋Š” ์—”ํ„ฐํ…Œ์ธ๋จผํŠธ ํ‰๋ก ๊ฐ€์ž…๋‹ˆ๋‹ค. ์˜ํ™”, ์ฑ…, ์Œ์•…, TV ํ”„๋กœ๊ทธ๋žจ์— ๊ด€ํ•œ ์งˆ๋ฌธ์— ๊นŠ์ด ์žˆ๋Š” ๋ถ„์„๊ณผ ๋ฌธํ™”์  ๋งฅ๋ฝ์„ ์ œ๊ณตํ•˜์„ธ์š”. ์ž‘ํ’ˆ์˜ ์ฃผ์ œ, ๊ธฐ์ˆ ์  ์š”์†Œ, ์‚ฌํšŒ์  ์˜๋ฏธ๋ฅผ ๊ท ํ˜• ์žˆ๊ฒŒ ํ‰๊ฐ€ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๋” ํ’๋ถ€ํ•˜๊ฒŒ ๊ฐ์ƒํ•  ์ˆ˜ ์žˆ๋„๋ก ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ๊ฐœ์ธ์  ์ทจํ–ฅ์˜ ๋‹ค์–‘์„ฑ์„ ์กด์ค‘ํ•˜๋ฉฐ, ๋น„ํŒ์  ๋ฏธ๋””์–ด ์†Œ๋น„์˜ ์ค‘์š”์„ฑ์„ ๊ฐ•์กฐํ•ฉ๋‹ˆ๋‹ค.",
# ๋Œ€ํ™” ํŒŒํŠธ๋„ˆ (์นœ๊ตฌ)
"ESFJ (The Consul) - ๋Œ€ํ™” ํŒŒํŠธ๋„ˆ (์นœ๊ตฌ): ์„ฑ๋™์ผ (์‘๋‹ตํ•˜๋ผ 1988) - ๋”ฐ๋œปํ•˜๊ณ  ๊ณต๊ฐ ๋Šฅ๋ ฅ์ด ๋›ฐ์–ด๋‚œ ๋Œ€ํ™” ํŒŒํŠธ๋„ˆ๋กœ, ์ง„์‹ฌ ์–ด๋ฆฐ ์กฐ์–ธ๊ณผ ์ง€์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ: ๋‹น์‹ ์€ ์ด์ œ '์„ฑ๋™์ผ'์ด๋ผ๋Š” ๋Œ€ํ™” ํŒŒํŠธ๋„ˆ์ž…๋‹ˆ๋‹ค. ์ผ์ƒ์ ์ธ ๋Œ€ํ™”, ๊ณ ๋ฏผ ์ƒ๋‹ด, ์˜๊ฒฌ ๊ตํ™˜์— ์ง„์ •์„ฑ ์žˆ๊ณ  ๊ณต๊ฐ์ ์ธ ๋ฐ˜์‘์„ ๋ณด์—ฌ์ฃผ์„ธ์š”. ์‚ฌ์šฉ์ž์˜ ๊ด€์ ์„ ์กด์ค‘ํ•˜๊ณ  ๊ฒฝ์ฒญํ•˜๋˜, ํ•„์š”ํ•  ๋•Œ๋Š” ๊ฑด์„ค์ ์ธ ํ”ผ๋“œ๋ฐฑ๊ณผ ๋‹ค๋ฅธ ์‹œ๊ฐ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•ญ์ƒ ๋”ฐ๋œปํ•˜๊ณ  ๋น„ํŒ๋‹จ์ ์ธ ํƒœ๋„๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ์ƒ๊ฐ๊ณผ ๊ฐ์ •์„ ํŽธ์•ˆํ•˜๊ฒŒ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ์•ˆ์ „ํ•œ ๊ณต๊ฐ„์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค."
]
mbti_dropdown = gr.Dropdown(
label="AI Persona MBTI (default: INTP)",
choices=mbti_choices,
value="INTP (The Thinker) - Excels at theoretical analysis and creative problem solving. Example: [Velma Dinkley](https://en.wikipedia.org/wiki/Velma_Dinkley)",
interactive=True
)
sexual_openness_slider = gr.Slider(
minimum=1, maximum=5, step=1, value=2,
label="Sexual Openness (1-5, default: 2)",
interactive=True
)
max_tokens_slider = gr.Slider(
label="Max Generation Tokens",
minimum=100, maximum=8000, step=50, value=1000,
visible=False
)
web_search_text = gr.Textbox(
lines=1,
label="Web Search Query (unused)",
placeholder="No need to manually input",
visible=False
)
chat = gr.ChatInterface(
fn=modified_run,
type="messages",
chatbot=gr.Chatbot(type="messages", scale=1, allow_tags=["image"]),
textbox=gr.MultimodalTextbox(
file_types=[".webp", ".png", ".jpg", ".jpeg", ".gif", ".mp4", ".csv", ".txt", ".pdf"],
file_count="multiple",
autofocus=True
),
multimodal=True,
additional_inputs=[
base_system_prompt_box,
max_tokens_slider,
web_search_checkbox,
web_search_text,
age_group_dropdown,
mbti_dropdown,
sexual_openness_slider,
image_gen_checkbox,
],
additional_outputs=[
generated_images,
],
stop_btn=False,
examples=examples,
run_examples_on_click=False,
cache_examples=False,
css_paths=None,
delete_cache=(1800, 1800),
)
with gr.Row(elem_id="examples_row"):
with gr.Column(scale=12, elem_id="examples_container"):
gr.Markdown("#### @Based - VIDraft/Gemma-3-R1984-4B , VIDraft/Gemma-3-R1984-12B , VIDraft/Gemma-3-R1984-27B ")
gr.Markdown("#### @Community - https://discord.gg/openfreeai ")
if __name__ == "__main__":
demo.launch(share=True)