File size: 14,069 Bytes
7391b54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc3c7d8
7391b54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0bbea4b
7391b54
 
bc3c7d8
7391b54
 
 
 
0bbea4b
 
 
bc3c7d8
 
 
 
 
 
 
 
 
 
0bbea4b
 
 
 
 
 
bc3c7d8
0bbea4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bc3c7d8
7391b54
 
0bbea4b
 
 
 
7391b54
 
0bbea4b
 
bc3c7d8
7391b54
0bbea4b
 
bc3c7d8
0bbea4b
bc3c7d8
0bbea4b
bc3c7d8
0bbea4b
 
 
 
 
bc3c7d8
0bbea4b
 
 
 
 
bc3c7d8
0bbea4b
 
 
bc3c7d8
 
 
0bbea4b
bc3c7d8
 
 
 
 
 
 
0bbea4b
bc3c7d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7391b54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0bbea4b
7391b54
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
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("""
    <div style="text-align: center; margin-bottom: 30px;">
        <h1 style="background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-size: 2.5em; margin-bottom: 10px;">
            Single-Turn Text Transformer (In, Out)
        </h1>
        <p style="font-size: 1.2em; color: #666; margin-bottom: 20px;">
          A pattern for simple form-based, system prompt defined, single turn text transformation UIs.
        </p>
    </div>
    """)
    
    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("""
    <script>
    document.addEventListener('DOMContentLoaded', function() {
        const savedApiKey = localStorage.getItem('openai_api_key');
        const savedSystemPrompt = localStorage.getItem('system_prompt');
        
        if (savedApiKey) {
            const apiKeyInput = document.querySelector('input[type="password"]');
            if (apiKeyInput) apiKeyInput.value = savedApiKey;
        }
        
        if (savedSystemPrompt) {
            const systemPromptInputs = document.querySelectorAll('textarea');
            if (systemPromptInputs.length > 0) {
                systemPromptInputs[0].value = savedSystemPrompt;
            }
        }
        
        // Save API key and system prompt on change
        document.addEventListener('input', function(e) {
            if (e.target.type === 'password') {
                localStorage.setItem('openai_api_key', e.target.value);
            }
            if (e.target.tagName === 'TEXTAREA' && e.target.placeholder.includes('system prompt')) {
                localStorage.setItem('system_prompt', e.target.value);
            }
        });
        
        // Enhanced copy functionality
        document.addEventListener('click', function(e) {
            if (e.target.textContent.includes('Copy Output')) {
                const outputTextarea = document.querySelectorAll('textarea');
                const outputText = outputTextarea[outputTextarea.length - 1].value;
                
                if (outputText && !outputText.startsWith('Error:')) {
                    navigator.clipboard.writeText(outputText).then(function() {
                        // Visual feedback
                        const originalText = e.target.textContent;
                        e.target.textContent = '✅ Copied!';
                        e.target.style.background = 'linear-gradient(45deg, #28a745 0%, #20c997 100%)';
                        
                        setTimeout(function() {
                            e.target.textContent = originalText;
                            e.target.style.background = 'linear-gradient(45deg, #56ab2f 0%, #a8e6cf 100%)';
                        }, 2000);
                    }).catch(function(err) {
                        console.error('Could not copy text: ', err);
                        alert('Copy failed. Please select and copy the text manually.');
                    });
                } else {
                    alert('No valid output text to copy.');
                }
            }
        });
    });
    </script>
    """)
    
 

if __name__ == "__main__":
    app.launch()