update
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { fetchGuides, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, reworkGuide as apiRework, createBaustein as apiCreateBaustein, fetchProjects, deleteProject as apiDeleteProject } from './api.js'
|
||||
import { fetchGuides, createGuide as apiCreate, deleteGuide, cancelGuide as apiCancel, reworkGuide as apiRework, createBaustein as apiCreateBaustein, fetchProjects, deleteProject as apiDeleteProject, fetchProviders } from './api.js'
|
||||
import TopicSidebar from './components/TopicSidebar.vue'
|
||||
import TopicDetail from './components/TopicDetail.vue'
|
||||
import BausteineView from './components/BausteineView.vue'
|
||||
@@ -21,6 +21,26 @@ const darkMode = ref(
|
||||
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
: localStorage.getItem('darkMode') === 'true',
|
||||
)
|
||||
const provider = ref(localStorage.getItem('provider') || 'claude')
|
||||
const providers = ref([])
|
||||
|
||||
function setProvider(id) {
|
||||
provider.value = id
|
||||
localStorage.setItem('provider', id)
|
||||
}
|
||||
|
||||
async function loadProviders() {
|
||||
try {
|
||||
providers.value = await fetchProviders()
|
||||
const current = providers.value.find((p) => p.id === provider.value)
|
||||
if (current && !current.available) {
|
||||
const fallback = providers.value.find((p) => p.available)
|
||||
if (fallback) setProvider(fallback.id)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Laden der Provider:', e)
|
||||
}
|
||||
}
|
||||
let pollTimer = null
|
||||
|
||||
function applyTheme() {
|
||||
@@ -149,7 +169,7 @@ function onHelpSelect(title) {
|
||||
|
||||
async function handleFormatClick({ format, instructions, reindex }) {
|
||||
if (!selectedTopic.value) return
|
||||
await apiCreate(selectedTopic.value, format, instructions, reindex || false)
|
||||
await apiCreate(selectedTopic.value, format, instructions, reindex || false, provider.value)
|
||||
await loadGuides()
|
||||
startPolling()
|
||||
}
|
||||
@@ -164,7 +184,7 @@ async function handleDeleteProject(name) {
|
||||
}
|
||||
|
||||
async function handleRework({ guideId, instructions }) {
|
||||
await apiRework(guideId, instructions)
|
||||
await apiRework(guideId, instructions, provider.value)
|
||||
await loadGuides()
|
||||
startPolling()
|
||||
}
|
||||
@@ -181,7 +201,7 @@ function handleShowBausteine() {
|
||||
|
||||
async function handleSidebarAddBaustein(title) {
|
||||
if (!selectedTopic.value) return
|
||||
await apiCreateBaustein(selectedTopic.value, title)
|
||||
await apiCreateBaustein(selectedTopic.value, title, '', provider.value)
|
||||
bausteineRefreshKey.value++
|
||||
}
|
||||
|
||||
@@ -236,7 +256,7 @@ function onVisibility() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadGuides(), loadProjects()])
|
||||
await Promise.all([loadGuides(), loadProjects(), loadProviders()])
|
||||
if (!selectedTopic.value && topics.value.length) {
|
||||
selectTopic(topics.value[0])
|
||||
}
|
||||
@@ -264,6 +284,9 @@ onUnmounted(() => {
|
||||
:bausteineActive="showBausteine"
|
||||
:pinned="sidebarPinned"
|
||||
:dark="darkMode"
|
||||
:provider="provider"
|
||||
:providers="providers"
|
||||
@setProvider="setProvider"
|
||||
@toggleDark="toggleDark"
|
||||
@select="selectTopic"
|
||||
@create="createTopic"
|
||||
@@ -282,6 +305,7 @@ onUnmounted(() => {
|
||||
/>
|
||||
<HelpChat
|
||||
v-if="showHelp"
|
||||
:provider="provider"
|
||||
@close="showHelp = false"
|
||||
@selectTopic="onHelpSelect"
|
||||
/>
|
||||
@@ -289,11 +313,13 @@ onUnmounted(() => {
|
||||
v-else-if="selectedTopic && showBausteine"
|
||||
:topic="selectedTopic"
|
||||
:refreshKey="bausteineRefreshKey"
|
||||
:provider="provider"
|
||||
/>
|
||||
<TopicDetail
|
||||
v-else-if="selectedTopic"
|
||||
:previewGuide="previewGuide"
|
||||
:dark="darkMode"
|
||||
:provider="provider"
|
||||
/>
|
||||
<div v-else class="empty-main">
|
||||
<p>Thema in der Sidebar anlegen oder auswählen.</p>
|
||||
|
||||
@@ -10,15 +10,20 @@ export async function fetchGuide(id) {
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function createGuide(topic, format, instructions = '', reindex = false) {
|
||||
export async function createGuide(topic, format, instructions = '', reindex = false, provider = 'claude') {
|
||||
const res = await fetch(`${BASE}/guides`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ topic, format, instructions, reindex }),
|
||||
body: JSON.stringify({ topic, format, instructions, reindex, provider }),
|
||||
})
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function fetchProviders() {
|
||||
const res = await fetch(`${BASE}/providers`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function fetchProjects() {
|
||||
const res = await fetch(`${BASE}/projects`)
|
||||
return res.json()
|
||||
@@ -28,11 +33,11 @@ export async function deleteProject(name) {
|
||||
await fetch(`${BASE}/projects/${encodeURIComponent(name)}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export async function reworkGuide(id, instructions) {
|
||||
export async function reworkGuide(id, instructions, provider = 'claude') {
|
||||
const res = await fetch(`${BASE}/guides/${id}/rework`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ instructions }),
|
||||
body: JSON.stringify({ instructions, provider }),
|
||||
})
|
||||
return res.json()
|
||||
}
|
||||
@@ -67,20 +72,20 @@ export async function setProgress(id, chapter, done) {
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function chatGuide(id, { section, outline, messages }) {
|
||||
export async function chatGuide(id, { section, outline, messages, provider = 'claude' }) {
|
||||
const res = await fetch(`${BASE}/guides/${id}/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ section, outline, messages }),
|
||||
body: JSON.stringify({ section, outline, messages, provider }),
|
||||
})
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function suggestTopics(problem) {
|
||||
export async function suggestTopics(problem, provider = 'claude') {
|
||||
const res = await fetch(`${BASE}/topic-suggestions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ problem }),
|
||||
body: JSON.stringify({ problem, provider }),
|
||||
})
|
||||
return res.json()
|
||||
}
|
||||
@@ -90,11 +95,11 @@ export async function fetchBausteine(topic) {
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function createBaustein(topic, title, instructions = '') {
|
||||
export async function createBaustein(topic, title, instructions = '', provider = 'claude') {
|
||||
const res = await fetch(`${BASE}/bausteine`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ topic, title, instructions }),
|
||||
body: JSON.stringify({ topic, title, instructions, provider }),
|
||||
})
|
||||
return res.json()
|
||||
}
|
||||
@@ -103,19 +108,19 @@ export async function deleteBaustein(id) {
|
||||
await fetch(`${BASE}/bausteine/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export async function reworkBaustein(id, instructions) {
|
||||
export async function reworkBaustein(id, instructions, provider = 'claude') {
|
||||
await fetch(`${BASE}/bausteine/${id}/rework`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ instructions }),
|
||||
body: JSON.stringify({ instructions, provider }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function sortBausteine(topic, instructions = '') {
|
||||
export async function sortBausteine(topic, instructions = '', provider = 'claude') {
|
||||
await fetch(`${BASE}/bausteine/sort?topic=${encodeURIComponent(topic)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ instructions }),
|
||||
body: JSON.stringify({ instructions, provider }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -129,8 +134,8 @@ export async function fetchSuggestions(topic) {
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function generateSuggestions(topic) {
|
||||
await fetch(`${BASE}/bausteine/suggestions/generate?topic=${encodeURIComponent(topic)}`, { method: 'POST' })
|
||||
export async function generateSuggestions(topic, provider = 'claude') {
|
||||
await fetch(`${BASE}/bausteine/suggestions/generate?topic=${encodeURIComponent(topic)}&provider=${encodeURIComponent(provider)}`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export async function fetchSuggestionsStatus(topic) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
const props = defineProps({
|
||||
topic: { type: String, required: true },
|
||||
refreshKey: { type: Number, default: 0 },
|
||||
provider: { type: String, default: 'claude' },
|
||||
})
|
||||
|
||||
const bausteine = ref([])
|
||||
@@ -65,7 +66,7 @@ async function handleAdd() {
|
||||
const info = newInfo.value.trim()
|
||||
newTitle.value = ''
|
||||
newInfo.value = ''
|
||||
const created = await createBaustein(props.topic, title, info)
|
||||
const created = await createBaustein(props.topic, title, info, props.provider)
|
||||
bausteine.value.push(created)
|
||||
reworkingSnapshots.set(created.id, created.updated_at)
|
||||
reworkingIds.value = new Set([...reworkingIds.value, created.id])
|
||||
@@ -97,14 +98,14 @@ async function handleRestore(s) {
|
||||
|
||||
async function handleRegenerate() {
|
||||
suggestionsLoading.value = true
|
||||
await generateSuggestions(props.topic)
|
||||
await generateSuggestions(props.topic, props.provider)
|
||||
startPolling()
|
||||
}
|
||||
|
||||
async function handleSort() {
|
||||
sortingActive.value = true
|
||||
const info = sortInfo.value.trim()
|
||||
await sortBausteine(props.topic, info)
|
||||
await sortBausteine(props.topic, info, props.provider)
|
||||
startSortPolling()
|
||||
}
|
||||
|
||||
@@ -127,7 +128,7 @@ async function handleRework(b) {
|
||||
reworkingSnapshots.set(b.id, b.updated_at)
|
||||
reworkingIds.value = new Set([...reworkingIds.value, b.id])
|
||||
reworkInputs.value[b.id] = ''
|
||||
await reworkBaustein(b.id, instructions)
|
||||
await reworkBaustein(b.id, instructions, props.provider)
|
||||
startBausteinPolling()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import { ref } from 'vue'
|
||||
import { suggestTopics } from '../api.js'
|
||||
|
||||
const props = defineProps({
|
||||
provider: { type: String, default: 'claude' },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'selectTopic'])
|
||||
|
||||
const problem = ref('')
|
||||
@@ -18,7 +22,7 @@ async function submit() {
|
||||
error.value = false
|
||||
suggestions.value = []
|
||||
try {
|
||||
const result = await suggestTopics(text)
|
||||
const result = await suggestTopics(text, props.provider)
|
||||
suggestions.value = Array.isArray(result) ? result : []
|
||||
} catch (e) {
|
||||
error.value = true
|
||||
|
||||
@@ -13,6 +13,7 @@ function renderMarkdown(text) {
|
||||
const props = defineProps({
|
||||
previewGuide: { type: Object, default: null },
|
||||
dark: { type: Boolean, default: false },
|
||||
provider: { type: String, default: 'claude' },
|
||||
})
|
||||
|
||||
const LANDSCAPE_FORMATS = ['OnePager', 'Cheatsheet']
|
||||
@@ -312,6 +313,7 @@ async function send() {
|
||||
section,
|
||||
outline,
|
||||
messages: messages.value,
|
||||
provider: props.provider,
|
||||
})
|
||||
messages.value.push({ role: 'assistant', content: res.reply || '…' })
|
||||
} catch {
|
||||
|
||||
@@ -12,9 +12,18 @@ const props = defineProps({
|
||||
bausteineActive: { type: Boolean, default: false },
|
||||
pinned: { type: Boolean, default: true },
|
||||
dark: { type: Boolean, default: false },
|
||||
provider: { type: String, default: 'claude' },
|
||||
providers: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['select', 'create', 'formatClick', 'deleteTopic', 'deleteProject', 'cancelGuide', 'deleteGuide', 'preview', 'rework', 'showBausteine', 'addBaustein', 'togglePin', 'sidebarLeave', 'openHelp', 'toggleDark'])
|
||||
const emit = defineEmits(['select', 'create', 'formatClick', 'deleteTopic', 'deleteProject', 'cancelGuide', 'deleteGuide', 'preview', 'rework', 'showBausteine', 'addBaustein', 'togglePin', 'sidebarLeave', 'openHelp', 'toggleDark', 'setProvider'])
|
||||
|
||||
function providerAvailable(id) {
|
||||
const p = props.providers.find((x) => x.id === id)
|
||||
return p ? p.available : true
|
||||
}
|
||||
|
||||
const PROVIDER_LABELS = { claude: 'Claude', minimax: 'MiniMax' }
|
||||
|
||||
const reindex = ref(false)
|
||||
|
||||
@@ -166,6 +175,16 @@ function confirmDeleteProject(name) {
|
||||
/>
|
||||
<button @click="submit" :disabled="!newTopic.trim()">+</button>
|
||||
</div>
|
||||
<div class="provider-toggle" v-if="providers.length">
|
||||
<button
|
||||
v-for="p in providers"
|
||||
:key="p.id"
|
||||
:class="{ active: p.id === provider }"
|
||||
:disabled="!p.available"
|
||||
:title="p.available ? '' : 'Nicht konfiguriert (CLI/Key fehlt)'"
|
||||
@click="emit('setProvider', p.id)"
|
||||
>{{ PROVIDER_LABELS[p.id] || p.id }}</button>
|
||||
</div>
|
||||
<div class="format-section" v-if="selectedTopic">
|
||||
<div class="progress-info" v-if="activeGenerations.length">
|
||||
<div v-for="(line, i) in activeGenerations" :key="i">{{ line }}</div>
|
||||
@@ -349,6 +368,43 @@ function confirmDeleteProject(name) {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
|
||||
.provider-toggle {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
padding: 0.5rem 0.75rem 0;
|
||||
}
|
||||
|
||||
.provider-toggle button {
|
||||
flex: 1;
|
||||
padding: 5px 8px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
border: 1px solid var(--border-strong);
|
||||
background: var(--bg);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.provider-toggle button:first-child {
|
||||
border-radius: 6px 0 0 6px;
|
||||
}
|
||||
|
||||
.provider-toggle button:last-child {
|
||||
border-radius: 0 6px 6px 0;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.provider-toggle button.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: var(--on-accent);
|
||||
}
|
||||
|
||||
.provider-toggle button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.topic-list {
|
||||
list-style: none;
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user