Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(templates): adding basic and clerk auth #231

Merged
merged 4 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions templates/vue/vue3-ai-chatbot-widget/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ VITE_PREVIEW_TEXT=

# VITE_FOOTER_DISCLAIMER: Footer disclaimer text
VITE_FOOTER_DISCLAIMER=

# VITE_AUTH_MODE: Authentication mode
# Options: basic or clerk.
VITE_AUTH_MODE=

# VITE_CLERK_PUBLIC_KEY: Clerk public key
VITE_CLERK_PUBLIC_KEY=
6 changes: 4 additions & 2 deletions templates/vue/vue3-ai-chatbot-widget/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
isOpenByDefault: Boolean,
isMaximizedByDefault: Boolean,
previewText: String,
footerDisclaimer: String
footerDisclaimer: String,
authMode: String
})

const chatWidget = reactive({
Expand All @@ -27,7 +28,8 @@
isOpenChat: props.isOpenByDefault,
isMaximizedChat: props.isMaximizedByDefault,
previewText: props.previewText,
footerDisclaimer: props.footerDisclaimer
footerDisclaimer: props.footerDisclaimer,
authMode: props.authMode
})

provide('chatWidget', chatWidget)
Expand Down
64 changes: 64 additions & 0 deletions templates/vue/vue3-ai-chatbot-widget/src/assets/auth-modal.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.auth-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}

.auth-modal {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.auth-form {
display: flex;
flex-direction: column;
gap: 1rem;
}

input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}

button {
padding: 0.5rem 1rem;
background: #f76707; /* Orange color */
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}

button:hover {
background: #e85d04; /* Darker orange on hover */
}

button:disabled {
opacity: 0.7;
cursor: not-allowed;
}

.error-message {
color: #dc3545;
font-size: 0.875rem;
padding: 0.5rem;
background: #fde8e8;
border-radius: 4px;
}

.loading-message {
color: #666;
font-size: 0.875rem;
text-align: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<template>
<div class="auth-modal-overlay">
<div class="auth-modal">
<h2>Authentication Required</h2>
<form class="auth-form" @submit.prevent="handleSubmit">
<div v-if="error" class="error-message">
{{ error }}
</div>
<div v-if="loading" class="loading-message">
Loading...
</div>
<input
type="password"
v-model="password"
placeholder="Enter password"
:disabled="loading"
autofocus
/>
<button
type="submit"
:disabled="loading"
>
{{ loading ? 'Signing in...' : 'Sign In' }}
</button>
</form>
</div>
</div>
</template>

<script setup>
import { ref } from 'vue'

const props = defineProps({
onSubmit: {
type: Function,
required: true
}
})

const password = ref('')
const loading = ref(false)
const error = ref('')

const handleSubmit = async () => {
if (!password.value) {
error.value = 'Password is required'
return
}

loading.value = true
error.value = ''

try {
await props.onSubmit(password.value)
} catch (err) {
error.value = err.message || 'Invalid password'
} finally {
loading.value = false
}
}
</script>

<style scoped>
@import '../assets/auth-modal.css';
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
>
<div
v-if="chatWidget.isOpenChat || chatWidget.isClosing"
class="fixed right-0 z-[55] border surface-ground surface-border transition-transform ease-in-out max-md:w-full max-md:h-full max-md:top-0 max-md:right-0"
:class="chatClass"
:class="[
...chatClass,
{ 'pointer-events-none': showAuthOverlay },
'fixed right-0 z-[55] border surface-ground surface-border transition-transform ease-in-out max-md:w-full max-md:h-full max-md:top-0 max-md:right-0'
]"
@transitionend="onTransitionEnd"
>
<div class="h-full flex flex-col">
Expand All @@ -32,22 +35,61 @@
</div>
</div>
</Transition>

<!-- Auth overlay - only show for basic auth -->
<Transition
enter-active-class="transition-all duration-300 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition-all duration-300 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div v-if="showAuthOverlay && chatWidget.authMode === 'basic'"
class="fixed inset-0 z-[60] bg-black/50 flex items-center justify-center">
<BasicAuthWindow :onSubmit="handleAuthSubmit" />
</div>
</Transition>
</template>

<script setup>
import { computed, inject } from 'vue'
import { computed, inject, ref } from 'vue'
import ChatHeader from './chat-header.vue'
import ChatBody from './chat-body.vue'
import ChatFooter from './chat-footer.vue'
import BasicAuthWindow from '../BasicAuthWindow.vue'
import { useAzionCopilot } from '../../composables/useAzionCopilot'
import { AuthService } from '../../services/auth'
import { CONSTANTS } from '../../core'

const chatWidget = inject('chatWidget')
const showAuthOverlay = ref(false)

defineOptions({ name: 'layout-chat' })

const { messages, sendMessage, cancelMessage, resetChat, isProcessingRequest, sendFeedback } =
const { messages, sendMessage, cancelMessage, resetChat, isProcessingRequest, sendFeedback, copilot } =
useAzionCopilot({ server: chatWidget.serverUrl })

copilot.on(CONSTANTS.EVENTS.AUTH_REQUIRED, () => {
console.log(chatWidget)
if (chatWidget.authMode === 'basic') {
showAuthOverlay.value = true
}
})

const handleAuthSubmit = async (password) => {
const authService = new AuthService({
authMode: 'basic',
copilotBackend: chatWidget.serverUrl.url
})

const result = await authService.fetchBasicAuth(password)
if (result.token) {
copilot.setAuthToken(result.token)
showAuthOverlay.value = false
}
}

const closeChat = () => {
chatWidget.isClosing = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
:class="classRoleApply"
>
<div
v-if="isSystem"
v-if="isAssistant"
class="flex gap-3 mt-1"
>
<Avatar />
</div>
<div class="message-content">
<div v-if="!isSystem">
<div v-if="!isAssistant">
<div
v-html="formattedMessage"
class="formatted-content"
Expand Down Expand Up @@ -149,14 +149,14 @@
}
})

const isSystem = computed(() => props.message.role === 'system')
const isAssistant = computed(() => props.message.role === 'assistant')
const messageReadingStatus = computed(
() => props.message.status === CONSTANTS.STATUS.MESSAGES.RESPONDING
)

const classRole = {
user: 'surface-300 ml-auto break-words w-fit rounded-lg h-fit px-4 py-3',
system: 'mr-auto w-full mt-3'
assistant: 'mr-auto w-full mt-3'
}

const classRoleApply = classRole[props.message.role]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { AzionCopilot, CONSTANTS } from '../core'

export function useAzionCopilot(config) {
const copilot = new AzionCopilot(config)

const token = sessionStorage.getItem('copilot_auth_token')
if (token) {
copilot.setAuthToken(token)
}

const messages = ref([])
const isProcessingRequest = ref(false)
Expand Down Expand Up @@ -40,6 +45,7 @@ export function useAzionCopilot(config) {
sendFeedback,
resetChat,
isProcessingRequest,
cancelMessage
cancelMessage,
copilot
}
}
Loading