AI chat panel with Edit/Chat modes + verify dropdown

- AI chat panel at bottom of editor (all 3 modes)
- Edit mode: AI modifies document directly (no explanations)
- Chat mode: AI answers questions about the document
- Verify dropdown: Spec Review, Grammar & Spelling, Summary
- Enter sends, Shift+Enter for newline
- /api/ai/chat endpoint with edit/chat system prompts
- Grammar and spec verify actions added to /api/ai/generate
This commit is contained in:
2026-05-27 10:44:56 +02:00
parent f46f57eded
commit 8223e72fe3
3 changed files with 221 additions and 2 deletions
+158
View File
@@ -119,6 +119,30 @@
<p v-else style="color:#6c7086;font-style:italic">Preview will appear here...</p>
</div>
</div>
<!-- AI Chat Panel -->
<div class="ai-chat-panel" v-if="currentFile">
<div class="ai-chat-modes">
<button :class="{active: aiChatMode === 'edit'}" @click="aiChatMode = 'edit'">Edit</button>
<button :class="{active: aiChatMode === 'chat'}" @click="aiChatMode = 'chat'">Chat</button>
<select v-model="aiVerifyType" @change="runVerify" class="ai-verify-select">
<option value="" disabled>Verify ▾</option>
<option value="spec">Spec Review</option>
<option value="grammar">Grammar & Spelling</option>
<option value="summary">Summary</option>
</select>
<span v-if="aiChatLoading" class="ai-loading">⏳</span>
</div>
<div class="ai-chat-output" v-if="aiChatResponse" v-html="renderMarkdown(aiChatResponse)"></div>
<div class="ai-chat-input">
<textarea
v-model="aiChatInput"
@keydown="aiChatKeydown"
placeholder="Ask AI to edit or chat... (Enter to send, Shift+Enter for newline)"
rows="2"
></textarea>
<button @click="sendAiChat" class="ai-send-btn">Send</button>
</div>
</div>
<!-- History Panel -->
<div class="history-panel" v-if="showHistory">
<div class="panel-header">
@@ -943,6 +967,12 @@ async function shareFile() {
// ─── AI ──────────────────────────────────────────────────────────────────────
const aiChatMode = ref('edit')
const aiChatInput = ref('')
const aiChatResponse = ref('')
const aiChatLoading = ref(false)
const aiVerifyType = ref('')
async function aiVerify() {
if (!currentFile.value) return
aiResult.value = 'Verifying...'
@@ -954,6 +984,55 @@ async function aiVerify() {
}
}
async function runVerify() {
if (!aiVerifyType.value || !currentFile.value) return
aiChatLoading.value = true
aiChatResponse.value = ''
try {
const res = await api('/api/ai/generate', { path: currentFile.value, action: aiVerifyType.value })
aiChatResponse.value = res.result || res.feedback || 'No response'
} catch (e) {
aiChatResponse.value = 'AI request failed.'
}
aiChatLoading.value = false
aiVerifyType.value = ''
}
function aiChatKeydown(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
sendAiChat()
}
}
async function sendAiChat() {
const msg = aiChatInput.value.trim()
if (!msg) return
aiChatLoading.value = true
aiChatResponse.value = ''
aiChatInput.value = ''
const action = aiChatMode.value === 'edit' ? 'edit' : 'chat'
try {
const res = await api('/api/ai/chat', {
path: currentFile.value,
content: content.value,
message: msg,
mode: action,
})
if (action === 'edit' && res.content) {
content.value = res.content
isDirty.value = true
aiChatResponse.value = 'Document updated.'
} else {
aiChatResponse.value = res.result || res.content || 'No response'
}
} catch (e) {
aiChatResponse.value = 'AI request failed. Check MH_AI_ENDPOINT.'
}
aiChatLoading.value = false
}
// ─── Collab ──────────────────────────────────────────────────────────────────
function toggleCollab() {
@@ -1465,6 +1544,85 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; b
}
.git-btn.dirty { border-color: var(--danger); }
/* ─── AI Chat Panel ───────────────────────────────────────────────────────── */
.ai-chat-panel {
height: 15%;
min-height: 100px;
max-height: 200px;
border-top: 1px solid var(--border);
display: flex;
flex-direction: column;
background: var(--bg-secondary);
}
.ai-chat-modes {
display: flex;
gap: 4px;
padding: 4px 8px;
align-items: center;
border-bottom: 1px solid var(--border);
}
.ai-chat-modes button {
background: transparent;
border: 1px solid var(--border);
color: var(--text-muted);
padding: 2px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.ai-chat-modes button.active {
background: var(--accent);
color: var(--bg-primary);
border-color: var(--accent);
}
.ai-verify-select {
background: var(--bg-primary);
border: 1px solid var(--border);
color: var(--text);
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
}
.ai-loading { font-size: 12px; }
.ai-chat-output {
flex: 1;
overflow-y: auto;
padding: 6px 10px;
font-size: 12px;
line-height: 1.5;
color: var(--text-muted);
}
.ai-chat-output p { margin: 0 0 6px; }
.ai-chat-input {
display: flex;
gap: 4px;
padding: 4px 8px;
border-top: 1px solid var(--border);
}
.ai-chat-input textarea {
flex: 1;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
padding: 4px 8px;
font-size: 12px;
font-family: inherit;
resize: none;
outline: none;
}
.ai-send-btn {
background: var(--accent);
color: var(--bg-primary);
border: none;
border-radius: 4px;
padding: 4px 12px;
cursor: pointer;
font-size: 12px;
}
.history-panel, .share-dialog, .ai-panel {
position: absolute;
right: 0;