2026-03-22 03:30:21 +00:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
|
|
const claudeConnected = ref(false)
|
|
|
|
|
const showClaudeLoginModal = ref(false)
|
|
|
|
|
|
|
|
|
|
function checkClaudeStatus() {
|
|
|
|
|
fetch('/aiui/api/claude/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'haiku', messages: [{ role: 'user', content: 'ping' }] }) })
|
|
|
|
|
.then(r => {
|
|
|
|
|
if (!r.ok) { claudeConnected.value = false; return }
|
|
|
|
|
const reader = r.body?.getReader()
|
|
|
|
|
if (!reader) return
|
|
|
|
|
const decoder = new TextDecoder()
|
|
|
|
|
let text = ''
|
|
|
|
|
function read(): Promise<void> {
|
|
|
|
|
return reader!.read().then(({ done, value }) => {
|
|
|
|
|
if (done) {
|
|
|
|
|
claudeConnected.value = !text.includes('Not logged in') && !text.includes('error')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
text += decoder.decode(value, { stream: true })
|
|
|
|
|
return read()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
read()
|
|
|
|
|
})
|
|
|
|
|
.catch(() => { claudeConnected.value = false })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onClaudeIframeLoad() {
|
|
|
|
|
window.addEventListener('message', handleClaudeLoginMessage)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleClaudeLoginMessage(e: MessageEvent) {
|
|
|
|
|
if (e.data?.type === 'claude-auth-success') {
|
|
|
|
|
claudeConnected.value = true
|
|
|
|
|
showClaudeLoginModal.value = false
|
|
|
|
|
window.removeEventListener('message', handleClaudeLoginMessage)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checkClaudeStatus()
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<!-- Claude Authentication Section -->
|
|
|
|
|
<div class="glass-card px-6 py-6 mb-6">
|
|
|
|
|
<h2 class="text-xl font-semibold text-white/96 mb-2">{{ t('settings.claudeAuth') }}</h2>
|
|
|
|
|
<p class="text-sm text-white/60 mb-6">{{ t('settings.claudeAuthDesc') }}</p>
|
2026-03-31 01:41:24 +01:00
|
|
|
<div class="bg-black/20 rounded-xl px-5 py-4 border border-white/10 mb-4" data-controller-ignore>
|
2026-03-22 03:30:21 +00:00
|
|
|
<div class="flex items-center gap-3 mb-2">
|
|
|
|
|
<svg class="w-5 h-5 shrink-0" :class="claudeConnected ? 'text-green-400' : 'text-white/40'" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path v-if="claudeConnected" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
|
|
|
<path v-else stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636a9 9 0 11-12.728 0M12 9v4m0 4h.01" />
|
|
|
|
|
</svg>
|
|
|
|
|
<p class="text-xs font-semibold text-white/60 uppercase tracking-wide">{{ t('settings.connectionStatus') }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="text-base font-medium" :class="claudeConnected ? 'text-green-400' : 'text-white/50'">
|
|
|
|
|
{{ claudeConnected ? t('common.connected') : t('settings.notConnected') }}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
@click="showClaudeLoginModal = true"
|
|
|
|
|
class="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg border transition-colors"
|
|
|
|
|
:class="claudeConnected
|
|
|
|
|
? 'border-white/20 text-white/70 hover:bg-white/5'
|
|
|
|
|
: 'glass-button-warning font-medium'"
|
|
|
|
|
>
|
|
|
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
|
|
|
|
|
</svg>
|
|
|
|
|
<span>{{ claudeConnected ? t('settings.reAuthenticate') : t('settings.loginWithClaude') }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Claude Login Modal -->
|
|
|
|
|
<Teleport to="body">
|
|
|
|
|
<div
|
|
|
|
|
v-if="showClaudeLoginModal"
|
|
|
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/10 backdrop-blur-md"
|
|
|
|
|
@click.self="showClaudeLoginModal = false"
|
|
|
|
|
>
|
|
|
|
|
<div class="glass-card p-0 max-w-lg w-full overflow-hidden" style="height: 480px">
|
|
|
|
|
<div class="flex items-center justify-between px-4 py-3 border-b border-white/10">
|
|
|
|
|
<h3 class="text-sm font-semibold text-white/80">{{ t('settings.claudeAuth') }}</h3>
|
|
|
|
|
<button @click="showClaudeLoginModal = false" class="text-white/50 hover:text-white/80 transition-colors">
|
|
|
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<iframe
|
|
|
|
|
src="/claude-login"
|
|
|
|
|
class="w-full border-0"
|
|
|
|
|
style="height: calc(100% - 49px)"
|
|
|
|
|
@load="onClaudeIframeLoad"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Teleport>
|
|
|
|
|
</template>
|