jan-blonde/jan-blonde-first-assistant icon
public
Published on 5/2/2025
My First Assistant

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.

Rules
Prompts
Models
Context
# 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
Vue3 documentationhttps://vuejs.org/guide/
Tailwind documentationhttps://tailwindcss.com/docs

Prompts

Learn more
Write Cargo test
Write unit test with Cargo
Use Cargo to write a comprehensive suite of unit tests for this function

Context

Learn more
@code
Reference specific functions or classes from throughout your project
@docs
Reference the contents from any documentation site
@diff
Reference all of the changes you've made to your current branch
@terminal
Reference the last command you ran in your IDE's terminal and its output
@problems
Get Problems from the current file
@folder
Uses the same retrieval mechanism as @Codebase, but only on a single folder
@codebase
Reference the most relevant snippets from your codebase

No Data configured

MCP Servers

Learn more

No MCP Servers configured