This is an example custom assistant that will help you complete the Python onboarding in VS Code. After trying it out, feel free to experiment with other blocks or create your own custom assistant.
mistral
# Vue + Tailwind AI Assistant Coding Guidelines
## Core Technologies
- **Vue 3** with Composition API as the foundation framework
- **Tailwind CSS** for styling
- **TypeScript** for type safety
## Architecture & Patterns
### Component Structure
- Use Vue 3's Composition API with `<script setup>` syntax for all components
- Follow a clear folder structure:
```
/src
/assets
/components
/ui # Reusable UI components
/layouts # Layout components
/features # Feature-specific components
/composables # Custom composables
/stores # Pinia stores
/services # API services
/utils # Utility functions
/types # TypeScript type definitions
/views # Page components
```
### Component Design
- Create atomic, single-responsibility components
- Use props validation with TypeScript interfaces
- Implement slots for flexible content insertion
- Use provide/inject for deep component communication where props would be cumbersome
## UI Development Guidelines
### Tailwind Usage
- Use Tailwind utility classes directly in templates
- Extract common patterns to components, not custom CSS classes
- Maintain consistent spacing using Tailwind's spacing scale
- Follow mobile-first responsive design principles
- Use Tailwind's color palette consistently and avoid custom colors when possible
### Example Component Pattern
```vue
<template>
<div class="rounded-lg p-4 shadow-md bg-white dark:bg-gray-800">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-medium text-gray-900 dark:text-white">{{ title }}</h3>
<button v-if="dismissable"
@click="$emit('dismiss')"
class="text-gray-400 hover:text-gray-500 transition-colors">
<span class="sr-only">Dismiss</span>
<XMarkIcon class="h-5 w-5" />
</button>
</div>
<div class="text-gray-700 dark:text-gray-300">
<slot></slot>
</div>
<div v-if="$slots.footer" class="mt-4 pt-3 border-t border-gray-200 dark:border-gray-700">
<slot name="footer"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { XMarkIcon } from '@heroicons/vue/24/outline'
interface Props {
title: string
dismissable?: boolean
}
defineProps<Props>()
defineEmits<{
(e: 'dismiss'): void
}>()
</script>
```
## State Management with Pinia
### Store Structure
- Create modular stores for different domains of your application
- Use TypeScript interfaces for strict typing
- Implement clear actions, getters, and state
### Example Store
```typescript
// stores/aiAssistant.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Message, AssistantState } from '@/types'
import { fetchCompletion } from '@/services/ai'
export const useAIAssistantStore = defineStore('aiAssistant', () => {
// State
const messages = ref<Message[]>([])
const isProcessing = ref(false)
const error = ref<string | null>(null)
// Getters
const conversationHistory = computed(() => messages.value)
const lastMessage = computed(() =>
messages.value.length > 0 ? messages.value[messages.value.length - 1] : null
)
// Actions
async function sendMessage(content: string) {
try {
isProcessing.value = true
error.value = null
// Add user message
messages.value.push({
role: 'user',
content,
timestamp: new Date()
})
// Get AI response
const response = await fetchCompletion(messages.value)
// Add AI response
messages.value.push({
role: 'assistant',
content: response.content,
timestamp: new Date()
})
} catch (err) {
error.value = err instanceof Error ? err.message : 'An unknown error occurred'
} finally {
isProcessing.value = false
}
}
function clearConversation() {
messages.value = []
}
return {
// State
messages,
isProcessing,
error,
// Getters
conversationHistory,
lastMessage,
// Actions
sendMessage,
clearConversation
}
})
```
## API Integration
### Service Pattern
- Create service modules for API integration
- Use axios or fetch with proper error handling
- Implement retry logic for network failures
### Example Service
```typescript
// services/ai.ts
import axios from 'axios'
import type { Message } from '@/types'
const API_URL = import.meta.env.VITE_AI_API_URL
const API_KEY = import.meta.env.VITE_AI_API_KEY
export async function fetchCompletion(messages: Message[]) {
try {
const response = await axios.post(`${API_URL}/completions`, {
messages: messages.map(({ role, content }) => ({ role, content })),
temperature: 0.7,
max_tokens: 1000,
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
}
})
return response.data
} catch (error) {
console.error('AI completion error:', error)
throw new Error('Failed to get AI response')
}
}
```
## Form Handling with VeeValidate + Zod
### Form Implementation
- Use VeeValidate for form management
- Use Zod for schema validation
- Create reusable form components
### Example Form Component
```vue
<template>
<form @submit="onSubmit" class="space-y-4">
<div>
<label for="message" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Your message
</label>
<textarea
id="message"
v-model="message"
:disabled="isSubmitting"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-white"
rows="4"
placeholder="Type your message here..."
></textarea>
<p v-if="errors.message" class="mt-1 text-sm text-red-600 dark:text-red-400">
{{ errors.message }}
</p>
</div>
<div class="flex justify-end">
<button
type="submit"
:disabled="isSubmitting"
class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
<span v-if="isSubmitting">
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Sending...
</span>
<span v-else>Send</span>
</button>
</div>
</form>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useForm } from 'vee-validate'
import { z } from 'zod'
import { toFormValidator } from '@vee-validate/zod'
import { useAIAssistantStore } from '@/stores/aiAssistant'
const aiStore = useAIAssistantStore()
// Define validation schema with Zod
const validationSchema = toFormValidator(
z.object({
message: z.string().min(1, 'Please enter a message').max(1000, 'Message is too long')
})
)
// Use VeeValidate's useForm
const { handleSubmit, errors } = useForm({
validationSchema
})
const message = ref('')
const isSubmitting = ref(false)
// Form submission handler
const onSubmit = handleSubmit(async (values) => {
try {
isSubmitting.value = true
await aiStore.sendMessage(message.value)
message.value = '' // Clear input after successful submission
} catch (error) {
console.error('Error sending message:', error)
} finally {
isSubmitting.value = false
}
})
</script>
```
## Data Fetching with Vue Query (TanStack Query)
### Query Pattern
- Implement Vue Query for complex data fetching scenarios
- Use query keys for caching and invalidation
- Handle loading, error, and success states
### Example Query Hook
```typescript
// composables/useAICompletion.ts
import { useQuery, useMutation } from '@tanstack/vue-query'
import { fetchCompletion } from '@/services/ai'
import type { Message } from '@/types'
export function useAICompletion() {
const messages = ref<Message[]>([])
// Mutation for sending messages
const sendMessageMutation = useMutation({
mutationFn: (content: string) => {
const newMessages = [...messages.value, {
role: 'user',
content,
timestamp: new Date()
}]
return fetchCompletion(newMessages)
},
onSuccess: (data, variables) => {
// Add user message
messages.value.push({
role: 'user',
content: variables,
timestamp: new Date()
})
// Add AI response
messages.value.push({
role: 'assistant',
content: data.content,
timestamp: new Date()
})
}
})
return {
messages,
sendMessage: sendMessageMutation.mutate,
isPending: sendMessageMutation.isPending,
error: sendMessageMutation.error
}
}
```
## Accessibility Guidelines
- Ensure proper contrast ratios for text using Tailwind's color system
- Use semantic HTML elements
- Implement proper ARIA attributes
- Ensure keyboard navigation works properly
- Test with screen readers
## Performance Optimization
- Use Vue's built-in features for lazy loading components
- Implement proper memoization using `computed` and `watchEffect`
- Optimize re-renders with `v-once` and `v-memo` where appropriate
- Use `shallowRef` for large objects that don't need deep reactivity
## Testing Strategy
- Unit test components with Vitest
- Use Vue Test Utils for component testing
- Implement Cypress for E2E testing
- Test API integrations with MSW (Mock Service Worker)
## Code Quality Tools
- ESLint with Vue plugin for code linting
- Prettier for code formatting
- TypeScript for static type checking
- Husky for pre-commit hooks
## AI Integration Best Practices
- Implement proper error handling for AI service failures
- Use streaming responses when available
- Maintain conversation history efficiently
- Implement rate limiting and user feedback
- Consider implementing local fallbacks for common queries
## Example AI Chat Component
```vue
<template>
<div class="flex flex-col h-full">
<!-- Chat Messages -->
<div class="flex-1 overflow-y-auto p-4 space-y-4" ref="messagesContainer">
<template v-if="messages.length === 0">
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
<ChatBubbleLeftIcon class="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No messages yet. Start a conversation!</p>
</div>
</template>
<div v-for="(message, index) in messages" :key="index" class="flex"
:class="message.role === 'user' ? 'justify-end' : 'justify-start'">
<div class="max-w-3/4 rounded-lg px-4 py-2"
:class="message.role === 'user'
? 'bg-indigo-100 text-indigo-900 dark:bg-indigo-900 dark:text-indigo-100'
: 'bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-100'">
<div class="prose dark:prose-invert" v-html="formatMessage(message.content)"></div>
</div>
</div>
<div v-if="isProcessing" class="flex justify-start">
<div class="max-w-3/4 rounded-lg px-4 py-2 bg-gray-100 dark:bg-gray-800">
<div class="flex items-center space-x-2">
<div class="w-2 h-2 rounded-full bg-gray-400 animate-pulse"></div>
<div class="w-2 h-2 rounded-full bg-gray-400 animate-pulse delay-75"></div>
<div class="w-2 h-2 rounded-full bg-gray-400 animate-pulse delay-150"></div>
</div>
</div>
</div>
</div>
<!-- Input Form -->
<div class="border-t border-gray-200 dark:border-gray-700 p-4">
<form @submit.prevent="sendMessage" class="flex space-x-2">
<textarea
v-model="newMessage"
@keydown.enter.prevent="sendMessage"
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-white"
placeholder="Type your message..."
rows="1"
:disabled="isProcessing"
></textarea>
<button
type="submit"
:disabled="isProcessing || !newMessage.trim()"
class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
<PaperAirplaneIcon class="h-5 w-5" />
</button>
</form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onUpdated, nextTick } from 'vue'
import { ChatBubbleLeftIcon, PaperAirplaneIcon } from '@heroicons/vue/24/outline'
import { useAIAssistantStore } from '@/stores/aiAssistant'
import { marked } from 'marked'
import DOMPurify from 'dompurify'
const aiStore = useAIAssistantStore()
const messages = computed(() => aiStore.conversationHistory)
const isProcessing = computed(() => aiStore.isProcessing)
const newMessage = ref('')
const messagesContainer = ref<HTMLElement | null>(null)
// Scroll to bottom when new messages are added
onUpdated(() => {
nextTick(() => {
if (messagesContainer.value) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
}
})
})
// Format message content with markdown
function formatMessage(content: string): string {
return DOMPurify.sanitize(marked(content))
}
// Send a new message
async function sendMessage() {
if (!newMessage.value.trim() || isProcessing.value) return
const message = newMessage.value
newMessage.value = ''
await aiStore.sendMessage(message)
}
</script>
```
## Deployment & CI/CD
- Use GitHub Actions for CI/CD pipeline
- Implement staging and production environments
- Use Vercel or Netlify for frontend deployment
- Implement proper environment variable management
Use Cargo to write a comprehensive suite of unit tests for this function
No Data configured
No MCP Servers configured