import gradio as gr
import openai
import os
from typing import Optional, Tuple
def transform_text(input_text: str, system_prompt: str, api_key: str) -> Tuple[str, str]:
if not api_key.strip():
return "Error: Please provide your OpenAI API key.", input_text
if not input_text.strip():
return "Error: Please provide input text to transform.", input_text
if not system_prompt.strip():
return "Error: Please provide a system prompt.", input_text
try:
client = openai.OpenAI(api_key=api_key.strip())
enhanced_system_prompt = f"""{system_prompt.strip()}
IMPORTANT: This is a single-turn text transformation workflow interface. You must respond with the complete transformed text only, without any additional commentary, explanations, or formatting before or after the content."""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": enhanced_system_prompt},
{"role": "user", "content": input_text.strip()}
],
temperature=0.7,
max_tokens=4000
)
output_text = response.choices[0].message.content
return output_text, ""
except openai.AuthenticationError:
return "Error: Invalid OpenAI API key. Please check your key and try again.", input_text
except openai.RateLimitError:
return "Error: Rate limit exceeded. Please try again later.", input_text
except openai.APIError as e:
return f"Error: OpenAI API error - {str(e)}", input_text
except Exception as e:
return f"Error: {str(e)}", input_text
def copy_to_clipboard(text: str) -> str:
return text
custom_css = """
.main-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.input-section, .output-section, .system-prompt-section {
margin-bottom: 20px;
}
.api-key-section {
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 4px solid #007bff;
}
.transform-button {
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
font-weight: bold;
font-size: 16px;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.copy-button {
background: linear-gradient(45deg, #56ab2f 0%, #a8e6cf 100%);
border: none;
color: white;
font-weight: bold;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
}
.warning-text {
color: #856404;
background-color: #fff3cd;
border: 1px solid #ffeaa7;
padding: 10px;
border-radius: 6px;
margin-bottom: 15px;
}
"""
# Create the Gradio interface
with gr.Blocks(css=custom_css, title="Text Transformation Model", theme=gr.themes.Soft()) as app:
gr.HTML("""
Single-Turn Text Transformer (In, Out)
A pattern for simple form-based, system prompt defined, single turn text transformation UIs.
""")
with gr.Tabs():
with gr.Tab("Transform Text"):
with gr.Row():
with gr.Column(scale=1):
gr.Image(
value="banner.jpg",
show_label=False,
show_download_button=False,
interactive=False,
container=False
)
with gr.Column(scale=2):
api_key_input = gr.Textbox(
label="OpenAI API Key",
placeholder="Enter your OpenAI API key (sk-...)",
type="password",
info="Your API key is stored locally in your browser for this session."
)
system_prompt_input = gr.Textbox(
label="System Prompt",
placeholder="Enter your system prompt here (e.g., 'Translate the following text to French', 'Summarize this text in bullet points', etc.)",
lines=4,
info="Define how the AI should transform your input text. This prompt will be enhanced automatically for single-turn processing."
)
with gr.Row():
with gr.Column(scale=1):
input_text = gr.Textbox(
label="Input Text",
placeholder="Paste or type your text here...",
lines=10,
info="Enter the text you want to transform"
)
transform_btn = gr.Button(
"✨ Transform Text",
variant="primary",
size="lg",
elem_classes=["transform-button"]
)
with gr.Column(scale=1):
output_text = gr.Textbox(
label="Output Text",
lines=10,
info="Transformed text will appear here",
interactive=False
)
copy_btn = gr.Button(
"Copy Output",
variant="secondary",
elem_classes=["copy-button"]
)
transform_btn.click(
fn=transform_text,
inputs=[input_text, system_prompt_input, api_key_input],
outputs=[output_text, input_text]
)
copy_btn.click(
fn=copy_to_clipboard,
inputs=[output_text],
outputs=[]
)
with gr.Tab("About"):
gr.Markdown("""
## Single Turn Text Transformer
- By: [Daniel Rosehill](https://danielrosehill.com) (11-Sep-2025)
This Space provides a simple implementation of an extremely foundational but effective use for large language models (LLMs): taking a text and transforming it into something else using a system prompt for guidance!
I call this Space a "model" of a "pattern" because an almost limitless variety of simple UIs can be developed around this principle by simply alternating the underlying system prompt (which in this demo is made editable with a short concatenated element).
I call tools like these the great forgotten gems of the AI landscape: just as this AI use-case reached a high degree of stability, maturity, and cost-effectiveness it has already been lumped into the bucket of legacy tech.
## What Do You Call An Assistant That's Not An Agent?
Single turn text transformation tools like this (which lack, I argue, even a proper name!) are stateless and non-agentic and thus locked out of the glamorous agent landscape. They are single turn and non-conversational so are clearly not chatbots. At best, they might be described as "assistants" - to the extent that agents have not already usurped the term. But as assistants now connote something much more agentic, they might lack no matter name than "using AI to transform text."
Text transformers have vast application: they can be used to change the person or level of formality of text; to restructure text from free-flowing formats into defined templates; or to clean up voice captured text, received from speech to text models, for downstream use in other systems. The model is very simple: the user prompt provides the text to be transformed and the system prompt provides the model with the desired transformation. The system prompt can also encapsulate few-shot examples to boost reliability. However, these workflows tend to execute well without even this safeguard.
## "Lived Experience"
I have implemented plenty of these.
To provide one pedestrian example: I have an agent like this whose task is to depersonalise system prompts so that I can open source them without random users getting addressed as "Daniel" or assumed to also live in Jerusalem. The system prompt asks the agent to take my system prompt and remove it of any details unique to my life - replace 'Daniel' with 'the user', etc. It's a simple in-out workflow that can (of course) be scripted and run in batches and programmatically but which is just as often more useful through a simple browser UI. And it doesn't get much simpler than a form in and form out!
Other reasons why I love these tools:
- They leverage a very foundational and intended use of LLMs. This is not a frontier use or cajoling LLMs into doing something we hope might work. It was what they were designed for. If you share my conviction that tools usually are best at what they were built for, this one's a winner.
- They are forgiving. You don't need a model branded as having PhD level reasoning to get these to work reliably well. Hence, they are cheap. And a huge advantage of affordability: you can spend plenty of time working on great system prompting until you achieve the perfect desired type of text reformatting.
- They are great, too, for just demonstrating the utility of AI away from the hype cycle. They do something that even the most advanced RegEx patterns can't. They work great locally, too.
- You just need a prompt: No tooling, no MCP integrations. Unless you're trying to transform encyclopedias in one go, they won't challenge the context window of modern models. In, out.
""")
with gr.Tab("Design"):
gr.Markdown("""
## Prompt Design for Single Turn Adherence
Effective prompt design patterns for non-JSON constrained prompting systems to ensure reliable single-turn text transformation workflows.
Instruction here: *IMPORTANT: This is a single-turn text transformation workflow interface. You must respond with the complete transformed text only, without any additional commentary, explanations, or formatting before or after the content.*
""")
gr.Image(
value="design/1.png",
show_label=False,
show_download_button=False,
interactive=False,
width=600
)
gr.Image(
value="design/2.png",
show_label=False,
show_download_button=False,
interactive=False,
width=600
)
gr.Image(
value="design/3.png",
show_label=False,
show_download_button=False,
interactive=False,
width=600
)
gr.HTML("""
""")
if __name__ == "__main__":
app.launch()