|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>Enhanced Collaborative Writing with AI (By Shift Mind AI Labs)</title> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
|
<style> |
|
|
body { background: #f5f7fa; font-family: 'Segoe UI', Arial, sans-serif; color: #333; margin: 0; } |
|
|
.container { max-width: 760px; margin: 40px auto; background: #fff; border-radius: 14px; box-shadow: 0 2px 18px rgba(44,62,80,0.10); padding: 36px 26px 22px 26px; } |
|
|
h1 { text-align: center; color: #154360; } |
|
|
label { font-weight: 500; margin-bottom: 7px; display: block; } |
|
|
[contenteditable="true"] { |
|
|
width: 100%; min-height: 180px; font-size: 1.07rem; border: 1px solid #bbb; border-radius: 7px; |
|
|
padding: 13px; background: #f6fafd; margin-bottom: 12px; box-sizing: border-box; outline: none; |
|
|
white-space: pre-wrap; line-height: 1.6; |
|
|
} |
|
|
.ai-text { |
|
|
background: #fffac2; |
|
|
color: #664d03; |
|
|
border-radius: 4px; |
|
|
padding: 0 2px; |
|
|
margin-right:2px; |
|
|
transition: background 0.3s; |
|
|
} |
|
|
.user-text { |
|
|
background: #e4ffd5; |
|
|
color: #217c33; |
|
|
border-radius: 4px; |
|
|
padding: 0 2px; |
|
|
margin-right:2px; |
|
|
transition: background 0.3s; |
|
|
} |
|
|
select, input[type="text"], input[type="password"] { |
|
|
width: 98%; padding: 7px 1%; border-radius: 7px; border: 1px solid #bbb; font-size: 1rem; margin-bottom: 8px; |
|
|
} |
|
|
select { width: 99%; } |
|
|
.role-desc { |
|
|
background: #f8f8fa; |
|
|
border-left: 5px solid #2471a3; |
|
|
padding: 12px 16px; |
|
|
border-radius: 6px; |
|
|
margin-bottom: 13px; |
|
|
font-size: 0.98rem; |
|
|
color: #555; |
|
|
min-height: 72px; |
|
|
} |
|
|
.button-bar { display: flex; gap: 14px; margin-top: 6px; margin-bottom: 10px; } |
|
|
button { background: #2e86de; color: #fff; border: none; border-radius: 7px; padding: 9px 24px; font-size: 1rem; cursor: pointer; transition: background 0.3s; } |
|
|
button:hover:not(:disabled) { background: #0b7dda; } |
|
|
button:disabled { background: #b2bec3; cursor: not-allowed; } |
|
|
#ai-status { font-size: 1rem; color: #2e86de; text-align: left; margin-bottom: 9px; min-height: 20px;} |
|
|
.final-output { background: #f1f8f5; border-radius: 8px; padding: 18px; margin-top: 22px; font-size: 1.07rem;} |
|
|
.api-key-label { color: #a93226; margin-top: 6px;} |
|
|
.writing-stats { |
|
|
background: #f8f9fa; |
|
|
border-radius: 6px; |
|
|
padding: 8px 12px; |
|
|
margin-bottom: 10px; |
|
|
font-size: 0.9rem; |
|
|
color: #666; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
} |
|
|
.context-panel { |
|
|
background: #f0f4f8; |
|
|
border-radius: 6px; |
|
|
padding: 10px; |
|
|
margin-bottom: 10px; |
|
|
font-size: 0.85rem; |
|
|
color: #555; |
|
|
border-left: 3px solid #3498db; |
|
|
} |
|
|
.continuation-mode { |
|
|
display: flex; |
|
|
gap: 10px; |
|
|
margin-bottom: 10px; |
|
|
align-items: center; |
|
|
} |
|
|
.continuation-mode label { |
|
|
margin: 0; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
.continuation-mode select { |
|
|
width: auto; |
|
|
margin: 0; |
|
|
} |
|
|
@media (max-width:800px){ .container{padding:12px 3vw;} } |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<h1>Collaborative Writing with AI (<span style="color:#2471a3;">By Shift Mind AI Labs</span>)</h1> |
|
|
<label for="api-key-input" class="api-key-label">OpenAI API Key <span style="font-weight:400;color:#666;">(never shared, stored only in this session)</span>:</label> |
|
|
<input id="api-key-input" type="password" placeholder="sk-..."> |
|
|
|
|
|
<label for="subject-input">Subject or Topic:</label> |
|
|
<input id="subject-input" type="text" placeholder="e.g., The importance of teamwork"> |
|
|
|
|
|
<label for="role-select">Select the Agent's Writing Role/Style:</label> |
|
|
<select id="role-select"> |
|
|
<option value="literature">Literature Author</option> |
|
|
<option value="researcher">Academic Researcher</option> |
|
|
<option value="journalist">Journalist</option> |
|
|
<option value="consultant">Consultant / Advisor</option> |
|
|
<option value="storyteller">Storyteller (Children's)</option> |
|
|
<option value="creative">Creative Writer</option> |
|
|
<option value="technical">Technical Writer</option> |
|
|
</select> |
|
|
<div id="role-desc" class="role-desc"></div> |
|
|
|
|
|
<button id="start-btn">Start Writing</button> |
|
|
|
|
|
<div id="editor-section" style="display:none;"> |
|
|
<div class="writing-stats"> |
|
|
<span id="word-count">Words: 0</span> |
|
|
<span id="char-count">Characters: 0</span> |
|
|
<span id="contribution-count">AI Contributions: 0</span> |
|
|
</div> |
|
|
|
|
|
<div class="continuation-mode"> |
|
|
<label for="continuation-style">AI Continuation Style:</label> |
|
|
<select id="continuation-style"> |
|
|
<option value="smooth">Smooth Flow</option> |
|
|
<option value="expand">Expand Ideas</option> |
|
|
<option value="contrast">Add Contrast</option> |
|
|
<option value="conclude">Move Toward Conclusion</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div class="continuation-mode"> |
|
|
<label for="sentence-count">AI Sentences to Add:</label> |
|
|
<select id="sentence-count"> |
|
|
<option value="1">1 sentence</option> |
|
|
<option value="2" selected>2 sentences</option> |
|
|
<option value="3">3 sentences</option> |
|
|
<option value="4">4 sentences</option> |
|
|
<option value="5">5 sentences</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div id="context-panel" class="context-panel" style="display:none;"> |
|
|
<strong>Current Context:</strong> <span id="context-summary"></span> |
|
|
</div> |
|
|
|
|
|
<label for="writing-area">Your Collaborative Writing</label> |
|
|
<div id="writing-area" contenteditable="true" spellcheck="true"></div> |
|
|
<div id="ai-status"></div> |
|
|
<div class="button-bar"> |
|
|
<button id="ai-continue-btn">AI Continue</button> |
|
|
<button id="ai-rephrase-btn" style="background:#8e44ad;">Rephrase Last</button> |
|
|
<button id="finish-btn" style="background:#27ae60;">Finish</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="final-output" class="final-output" style="display:none;"></div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const agentRoles = { |
|
|
literature: { |
|
|
name: "Literature Author", |
|
|
description: `• Rich imagery, metaphors, and emotional depth<br> |
|
|
• Character development and atmospheric setting<br> |
|
|
• Poetic language with symbolic meaning<br> |
|
|
• Builds narrative tension and mood progressively`, |
|
|
system: `You are a masterful literary author. Write with rich, evocative language that creates vivid imagery and emotional resonance. Use metaphors, symbolism, and sensory details. Develop characters and atmosphere. Each contribution should deepen the narrative and maintain literary quality while flowing naturally from the existing text.` |
|
|
}, |
|
|
researcher: { |
|
|
name: "Academic Researcher", |
|
|
description: `• Evidence-based and analytically rigorous<br> |
|
|
• Structured arguments with logical progression<br> |
|
|
• Technical precision and scholarly tone<br> |
|
|
• Builds comprehensive understanding systematically`, |
|
|
system: `You are a distinguished academic researcher. Write with precision, analytical depth, and scholarly rigor. Support points with evidence, maintain logical structure, and use appropriate technical language. Each contribution should advance the argument or analysis while building on previous points systematically.` |
|
|
}, |
|
|
journalist: { |
|
|
name: "Journalist", |
|
|
description: `• Clear, factual, and compelling narrative<br> |
|
|
• Investigative depth with human interest<br> |
|
|
• Balanced perspective with key details<br> |
|
|
• Engages readers while informing thoroughly`, |
|
|
system: `You are an experienced journalist. Write with clarity, accuracy, and engaging narrative flow. Focus on key facts, human interest, and balanced reporting. Each contribution should advance the story, reveal new information, or provide deeper context while maintaining journalistic integrity.` |
|
|
}, |
|
|
consultant: { |
|
|
name: "Consultant / Advisor", |
|
|
description: `• Strategic insights with actionable recommendations<br> |
|
|
• Problem-solving framework and solutions<br> |
|
|
• Professional tone with clear next steps<br> |
|
|
• Builds comprehensive strategic understanding`, |
|
|
system: `You are a senior strategic consultant. Write with authority, providing actionable insights and recommendations. Use frameworks, strategic thinking, and solution-oriented language. Each contribution should build the case, provide analysis, or offer concrete next steps that advance the overall strategic narrative.` |
|
|
}, |
|
|
storyteller: { |
|
|
name: "Storyteller (Children's)", |
|
|
description: `• Engaging, age-appropriate language with wonder<br> |
|
|
• Clear moral lessons through entertaining narrative<br> |
|
|
• Rhythmic, memorable phrases and repetition<br> |
|
|
• Builds excitement and learning progressively`, |
|
|
system: `You are a beloved children's storyteller. Write with warmth, wonder, and age-appropriate language. Use gentle repetition, clear moral lessons, and engaging narrative elements. Each contribution should advance the story, develop characters, or reinforce the central message while maintaining child-friendly appeal.` |
|
|
}, |
|
|
creative: { |
|
|
name: "Creative Writer", |
|
|
description: `• Innovative narrative techniques and perspectives<br> |
|
|
• Experimental language and unique voice<br> |
|
|
• Unexpected connections and creative insights<br> |
|
|
• Pushes boundaries while maintaining coherence`, |
|
|
system: `You are an innovative creative writer. Experiment with language, perspective, and narrative techniques. Bring fresh insights and unexpected connections. Each contribution should surprise while maintaining coherence, advancing the creative vision, and building on established themes in novel ways.` |
|
|
}, |
|
|
technical: { |
|
|
name: "Technical Writer", |
|
|
description: `• Clear, precise explanations of complex concepts<br> |
|
|
• Step-by-step logical progression<br> |
|
|
• Practical examples and applications<br> |
|
|
• Builds comprehensive technical understanding`, |
|
|
system: `You are an expert technical writer. Explain complex concepts with clarity and precision. Use logical progression, practical examples, and clear structure. Each contribution should build technical understanding, provide concrete details, or advance the explanation while remaining accessible and well-organized.` |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const continuationStyles = { |
|
|
smooth: "Continue the narrative flow naturally, maintaining the same tone and building on the immediate context.", |
|
|
expand: "Elaborate on the ideas presented, adding depth, examples, or additional perspectives to enrich the content.", |
|
|
contrast: "Introduce a contrasting viewpoint, alternative perspective, or counterpoint to create intellectual tension.", |
|
|
conclude: "Guide the writing toward a natural conclusion, summarizing key points or bringing the narrative to closure." |
|
|
}; |
|
|
|
|
|
|
|
|
let writingContext = { |
|
|
subject: "", |
|
|
overallTheme: "", |
|
|
keyPoints: [], |
|
|
lastUserContribution: "", |
|
|
narrativeDirection: "", |
|
|
toneConsistency: "", |
|
|
aiContributions: 0, |
|
|
totalWords: 0 |
|
|
}; |
|
|
|
|
|
|
|
|
const apiKeyInput = document.getElementById('api-key-input'); |
|
|
const subjectInput = document.getElementById('subject-input'); |
|
|
const roleSelect = document.getElementById('role-select'); |
|
|
const roleDesc = document.getElementById('role-desc'); |
|
|
const startBtn = document.getElementById('start-btn'); |
|
|
const editorSection = document.getElementById('editor-section'); |
|
|
const writingArea = document.getElementById('writing-area'); |
|
|
const aiContinueBtn = document.getElementById('ai-continue-btn'); |
|
|
const aiRephraseBtn = document.getElementById('ai-rephrase-btn'); |
|
|
const aiStatus = document.getElementById('ai-status'); |
|
|
const finishBtn = document.getElementById('finish-btn'); |
|
|
const finalOutput = document.getElementById('final-output'); |
|
|
const continuationStyle = document.getElementById('continuation-style'); |
|
|
const sentenceCount = document.getElementById('sentence-count'); |
|
|
const contextPanel = document.getElementById('context-panel'); |
|
|
const contextSummary = document.getElementById('context-summary'); |
|
|
const wordCount = document.getElementById('word-count'); |
|
|
const charCount = document.getElementById('char-count'); |
|
|
const contributionCount = document.getElementById('contribution-count'); |
|
|
|
|
|
|
|
|
function updateRoleDesc() { |
|
|
const role = roleSelect.value; |
|
|
roleDesc.innerHTML = `<b>${agentRoles[role].name}:</b><br>${agentRoles[role].description}`; |
|
|
} |
|
|
updateRoleDesc(); |
|
|
roleSelect.onchange = updateRoleDesc; |
|
|
|
|
|
|
|
|
function updateStats() { |
|
|
const text = writingArea.textContent || ""; |
|
|
const words = text.trim() ? text.trim().split(/\s+/).length : 0; |
|
|
const chars = text.length; |
|
|
|
|
|
wordCount.textContent = `Words: ${words}`; |
|
|
charCount.textContent = `Characters: ${chars}`; |
|
|
contributionCount.textContent = `AI Contributions: ${writingContext.aiContributions}`; |
|
|
writingContext.totalWords = words; |
|
|
} |
|
|
|
|
|
|
|
|
function analyzeContext() { |
|
|
const fullText = writingArea.textContent || ""; |
|
|
const sentences = fullText.split(/[.!?]+/).filter(s => s.trim().length > 0); |
|
|
|
|
|
if (sentences.length > 0) { |
|
|
const lastSentences = sentences.slice(-3).join('. '); |
|
|
contextSummary.textContent = lastSentences.substring(0, 100) + (lastSentences.length > 100 ? '...' : ''); |
|
|
contextPanel.style.display = 'block'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function callOpenAI(apiKey, agentSystemPrompt, context, isRephrase = false) { |
|
|
const fullText = writingArea.textContent || ""; |
|
|
const {allText, lastUserText, textSegments} = getEnhancedContext(writingArea); |
|
|
|
|
|
|
|
|
const contextPrompt = ` |
|
|
WRITING CONTEXT: |
|
|
- Subject: "${writingContext.subject}" |
|
|
- Current word count: ${writingContext.totalWords} |
|
|
- AI contributions so far: ${writingContext.aiContributions} |
|
|
- Continuation style requested: ${continuationStyles[continuationStyle.value]} |
|
|
- Last user contribution: "${lastUserText}" |
|
|
|
|
|
FULL TEXT SO FAR: |
|
|
"""${allText}""" |
|
|
|
|
|
INSTRUCTIONS: |
|
|
${isRephrase ? |
|
|
'Rephrase the last AI contribution to be more engaging and better integrated with the user\'s writing. Keep the same general meaning but improve flow and style.' : |
|
|
`Write exactly ${sentenceCount.value} sentence${sentenceCount.value > 1 ? 's' : ''} that continue this collaborative piece naturally. ${continuationStyles[continuationStyle.value]} |
|
|
|
|
|
CRITICAL REQUIREMENTS: |
|
|
- Write EXACTLY ${sentenceCount.value} sentence${sentenceCount.value > 1 ? 's' : ''} - no more, no less |
|
|
- Do NOT repeat or echo the user's last contribution |
|
|
- Maintain consistent tone and style throughout |
|
|
- Build on existing themes and narrative direction |
|
|
- Each sentence should advance the content meaningfully |
|
|
- Ensure smooth transitions from previous text |
|
|
- Respond ONLY with the continuation text, no explanations` |
|
|
}`; |
|
|
|
|
|
const messages = [ |
|
|
{ role: "system", content: agentSystemPrompt }, |
|
|
{ role: "user", content: contextPrompt } |
|
|
]; |
|
|
|
|
|
const body = { |
|
|
model: "gpt-4o-mini", |
|
|
messages, |
|
|
max_tokens: isRephrase ? 100 : Math.min(200, parseInt(sentenceCount.value) * 40), |
|
|
temperature: 0.7, |
|
|
presence_penalty: 0.6, |
|
|
frequency_penalty: 0.3 |
|
|
}; |
|
|
|
|
|
const response = await fetch("https://api.openai.com/v1/chat/completions", { |
|
|
method: "POST", |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Authorization": "Bearer " + apiKey |
|
|
}, |
|
|
body: JSON.stringify(body) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
let msg = "Unknown error"; |
|
|
try { msg = (await response.json()).error?.message || msg; } catch { } |
|
|
throw new Error(msg); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
const aiText = data.choices[0].message.content.trim(); |
|
|
|
|
|
|
|
|
if (!isRephrase) { |
|
|
writingContext.aiContributions++; |
|
|
writingContext.lastUserContribution = lastUserText; |
|
|
} |
|
|
|
|
|
return aiText; |
|
|
} |
|
|
|
|
|
|
|
|
function getEnhancedContext(container) { |
|
|
let allText = ''; |
|
|
let lastUserText = ''; |
|
|
let textSegments = []; |
|
|
let currentSegment = { type: 'user', text: '' }; |
|
|
|
|
|
const processNode = (node) => { |
|
|
if (node.nodeType === 3) { |
|
|
const text = node.textContent; |
|
|
if (text.trim()) { |
|
|
allText += text; |
|
|
currentSegment.text += text; |
|
|
if (currentSegment.type === 'user') { |
|
|
lastUserText = text.trim(); |
|
|
} |
|
|
} |
|
|
} else if (node.nodeType === 1) { |
|
|
if (node.classList.contains('ai-text')) { |
|
|
if (currentSegment.text.trim()) { |
|
|
textSegments.push({...currentSegment}); |
|
|
} |
|
|
currentSegment = { type: 'ai', text: node.textContent }; |
|
|
allText += node.textContent; |
|
|
textSegments.push({...currentSegment}); |
|
|
currentSegment = { type: 'user', text: '' }; |
|
|
} else if (node.classList.contains('user-text')) { |
|
|
allText += node.textContent; |
|
|
currentSegment.text += node.textContent; |
|
|
lastUserText = node.textContent.trim(); |
|
|
} else if (node.tagName === 'BR') { |
|
|
allText += '\n'; |
|
|
currentSegment.text += '\n'; |
|
|
} else { |
|
|
for (let child of node.childNodes) { |
|
|
processNode(child); |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
for (let node of container.childNodes) { |
|
|
processNode(node); |
|
|
} |
|
|
|
|
|
if (currentSegment.text.trim()) { |
|
|
textSegments.push(currentSegment); |
|
|
} |
|
|
|
|
|
return { allText: allText.trim(), lastUserText: lastUserText.trim(), textSegments }; |
|
|
} |
|
|
|
|
|
|
|
|
startBtn.onclick = async () => { |
|
|
const apiKey = apiKeyInput.value.trim(); |
|
|
if (!apiKey.startsWith('sk-') || apiKey.length < 30) { |
|
|
apiKeyInput.value = ""; |
|
|
apiKeyInput.focus(); |
|
|
apiKeyInput.placeholder = "Please enter a valid OpenAI API key!"; |
|
|
return; |
|
|
} |
|
|
|
|
|
const subject = subjectInput.value.trim(); |
|
|
if (!subject) { |
|
|
subjectInput.focus(); |
|
|
subjectInput.placeholder = "Please enter a subject!"; |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
writingContext.subject = subject; |
|
|
writingContext.aiContributions = 0; |
|
|
writingContext.totalWords = 0; |
|
|
|
|
|
|
|
|
apiKeyInput.disabled = true; |
|
|
subjectInput.disabled = true; |
|
|
roleSelect.disabled = true; |
|
|
startBtn.disabled = true; |
|
|
|
|
|
|
|
|
editorSection.style.display = ''; |
|
|
aiStatus.textContent = "AI is crafting the opening..."; |
|
|
aiContinueBtn.disabled = true; |
|
|
aiRephraseBtn.disabled = true; |
|
|
writingArea.innerHTML = ""; |
|
|
|
|
|
try { |
|
|
const agentSystemPrompt = agentRoles[roleSelect.value].system; |
|
|
const aiText = await callOpenAI(apiKey, agentSystemPrompt, { subject, isOpening: true }); |
|
|
|
|
|
writingArea.innerHTML = `<span class="ai-text">${escapeHTML(aiText)}</span>`; |
|
|
placeCaretAtEnd(writingArea); |
|
|
|
|
|
aiStatus.textContent = "Great start! Add your thoughts and click 'AI Continue' when ready."; |
|
|
aiContinueBtn.disabled = false; |
|
|
writingArea.focus(); |
|
|
|
|
|
updateStats(); |
|
|
analyzeContext(); |
|
|
|
|
|
} catch (e) { |
|
|
aiStatus.textContent = "Error: " + e.message; |
|
|
apiKeyInput.disabled = false; |
|
|
startBtn.disabled = false; |
|
|
roleSelect.disabled = false; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
aiContinueBtn.onclick = async () => { |
|
|
aiStatus.textContent = "AI is continuing the narrative..."; |
|
|
aiContinueBtn.disabled = true; |
|
|
aiRephraseBtn.disabled = true; |
|
|
writingArea.contentEditable = "false"; |
|
|
|
|
|
|
|
|
writingArea.innerHTML = buildHTMLWithUserHighlight(writingArea); |
|
|
|
|
|
try { |
|
|
const apiKey = apiKeyInput.value.trim(); |
|
|
const agentSystemPrompt = agentRoles[roleSelect.value].system; |
|
|
const aiText = await callOpenAI(apiKey, agentSystemPrompt, writingContext); |
|
|
|
|
|
insertAIText(aiText); |
|
|
|
|
|
aiStatus.textContent = "Your turn! Edit, add more, or click 'AI Continue' for the next contribution."; |
|
|
writingArea.contentEditable = "true"; |
|
|
aiContinueBtn.disabled = false; |
|
|
aiRephraseBtn.disabled = false; |
|
|
|
|
|
placeCaretAtEnd(writingArea); |
|
|
writingArea.focus(); |
|
|
|
|
|
updateStats(); |
|
|
analyzeContext(); |
|
|
|
|
|
} catch (e) { |
|
|
aiStatus.textContent = "Error: " + e.message; |
|
|
writingArea.contentEditable = "true"; |
|
|
aiContinueBtn.disabled = false; |
|
|
aiRephraseBtn.disabled = false; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
aiRephraseBtn.onclick = async () => { |
|
|
const aiSpans = writingArea.querySelectorAll('.ai-text'); |
|
|
if (aiSpans.length === 0) { |
|
|
aiStatus.textContent = "No AI contributions to rephrase yet."; |
|
|
return; |
|
|
} |
|
|
|
|
|
aiStatus.textContent = "AI is rephrasing the last contribution..."; |
|
|
aiContinueBtn.disabled = true; |
|
|
aiRephraseBtn.disabled = true; |
|
|
|
|
|
try { |
|
|
const apiKey = apiKeyInput.value.trim(); |
|
|
const agentSystemPrompt = agentRoles[roleSelect.value].system; |
|
|
const newText = await callOpenAI(apiKey, agentSystemPrompt, writingContext, true); |
|
|
|
|
|
|
|
|
const lastAISpan = aiSpans[aiSpans.length - 1]; |
|
|
lastAISpan.textContent = newText; |
|
|
|
|
|
aiStatus.textContent = "Rephrased! Continue editing or add more content."; |
|
|
aiContinueBtn.disabled = false; |
|
|
aiRephraseBtn.disabled = false; |
|
|
|
|
|
updateStats(); |
|
|
analyzeContext(); |
|
|
|
|
|
} catch (e) { |
|
|
aiStatus.textContent = "Error: " + e.message; |
|
|
aiContinueBtn.disabled = false; |
|
|
aiRephraseBtn.disabled = false; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
finishBtn.onclick = () => { |
|
|
editorSection.style.display = "none"; |
|
|
finalOutput.style.display = ""; |
|
|
finalOutput.innerHTML = ` |
|
|
<h2>Final Collaborative Writing</h2> |
|
|
<div style="margin-bottom: 15px; font-size: 0.9rem; color: #666;"> |
|
|
<strong>Statistics:</strong> ${writingContext.totalWords} words, ${writingContext.aiContributions} AI contributions |
|
|
</div> |
|
|
<hr> |
|
|
<div style="white-space: pre-line; line-height: 1.6;">${writingArea.innerHTML}</div> |
|
|
<hr><br> |
|
|
<a href="javascript:window.location.reload()" style="color:#2980b9; text-decoration: none;">Start New Writing Session</a> |
|
|
`; |
|
|
}; |
|
|
|
|
|
|
|
|
function escapeHTML(str) { |
|
|
return str.replace(/[&<>"']/g, function (m) { |
|
|
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[m]; |
|
|
}); |
|
|
} |
|
|
|
|
|
function buildHTMLWithUserHighlight(container) { |
|
|
let html = ''; |
|
|
for (let node of container.childNodes) { |
|
|
if (node.nodeType === 1 && node.classList.contains('ai-text')) { |
|
|
html += node.outerHTML; |
|
|
} else if (node.nodeType === 3) { |
|
|
let txt = node.textContent; |
|
|
if (txt.trim()) { |
|
|
html += `<span class="user-text">${escapeHTML(txt)}</span>`; |
|
|
} else { |
|
|
html += escapeHTML(txt); |
|
|
} |
|
|
} else if (node.nodeType === 1 && node.tagName === "BR") { |
|
|
html += '<br>'; |
|
|
} else if (node.nodeType === 1 && !node.classList.contains('ai-text') && !node.classList.contains('user-text')) { |
|
|
|
|
|
let innerText = node.textContent; |
|
|
if (innerText.trim()) { |
|
|
html += `<span class="user-text">${escapeHTML(innerText)}</span>`; |
|
|
} |
|
|
} else if (node.nodeType === 1) { |
|
|
html += node.outerHTML; |
|
|
} |
|
|
} |
|
|
return html; |
|
|
} |
|
|
|
|
|
function insertAIText(aiText) { |
|
|
let html = writingArea.innerHTML.replace(/\s*$/, ""); |
|
|
let toInsert = `<span class="ai-text">${escapeHTML(aiText)}</span>`; |
|
|
|
|
|
|
|
|
const currentText = writingArea.textContent || ""; |
|
|
if (currentText && !currentText.match(/[.!?]\s*$/)) { |
|
|
if (aiText.match(/^[A-Z]/)) { |
|
|
toInsert = ". " + toInsert; |
|
|
} else { |
|
|
toInsert = " " + toInsert; |
|
|
} |
|
|
} else if (currentText) { |
|
|
toInsert = " " + toInsert; |
|
|
} |
|
|
|
|
|
writingArea.innerHTML = html + toInsert; |
|
|
} |
|
|
|
|
|
function placeCaretAtEnd(el) { |
|
|
el.focus(); |
|
|
if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") { |
|
|
let range = document.createRange(); |
|
|
range.selectNodeContents(el); |
|
|
range.collapse(false); |
|
|
let sel = window.getSelection(); |
|
|
sel.removeAllRanges(); |
|
|
sel.addRange(range); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
writingArea.addEventListener('input', function() { |
|
|
writingArea.innerHTML = buildHTMLWithUserHighlight(writingArea); |
|
|
placeCaretAtEnd(writingArea); |
|
|
updateStats(); |
|
|
analyzeContext(); |
|
|
}); |
|
|
|
|
|
|
|
|
let autoSaveData = {}; |
|
|
setInterval(() => { |
|
|
if (writingArea.innerHTML) { |
|
|
autoSaveData = { |
|
|
content: writingArea.innerHTML, |
|
|
subject: writingContext.subject, |
|
|
role: roleSelect.value, |
|
|
stats: { |
|
|
words: writingContext.totalWords, |
|
|
contributions: writingContext.aiContributions |
|
|
} |
|
|
}; |
|
|
} |
|
|
}, 30000); |
|
|
</script> |
|
|
</body> |
|
|
</html> |