|
7 | 7 | z-40: Modal backdrops
|
8 | 8 | z-50: Modals/dialogs/overlays
|
9 | 9 | -->
|
10 |
| - <div class="h-screen flex flex-col bg-white dark:bg-oled-black"> |
| 10 | + <div id="app" class="app-container h-screen flex flex-col" :class="{ 'coach-artie-mode': isCoachArtieMode }"> |
11 | 11 | <!-- Base App Shell -->
|
12 | 12 | <div v-if="isAuthenticated" class="relative flex flex-col flex-1 min-h-0">
|
13 | 13 | <!-- Title Bar -->
|
|
71 | 71 | <ChatInput v-model="messageInput" :is-loading="isSending" :has-valid-key="hasValidKey"
|
72 | 72 | :show-mention-popup="showMentionPopup" :is-searching-files="isSearchingFiles"
|
73 | 73 | :has-obsidian-vault="hasObsidianVault" :obsidian-search-results="obsidianSearchResults"
|
74 |
| - @send="handleSendMessage" @mention-popup="(show) => showMentionPopup = show" |
75 |
| - @obsidian-link="handleObsidianLink" @input="(query) => searchQuery = query" /> |
| 74 | + :obsidian-vault-path="obsidian.vaultPath.value" @send="handleSendMessage" |
| 75 | + @mention-popup="(show) => showMentionPopup = show" @obsidian-link="handleObsidianLink" |
| 76 | + @input="(query) => searchQuery = query" /> |
76 | 77 | </div>
|
77 | 78 | </main>
|
78 | 79 | </div>
|
@@ -144,6 +145,7 @@ import { useSupabase } from './composables/useSupabase'
|
144 | 145 | import { useAIChat } from './composables/useAIChat'
|
145 | 146 | import { useStore } from './lib/store'
|
146 | 147 | import { useObsidianFiles } from './composables/useObsidianFiles'
|
| 148 | +import { useCoachArtie } from './composables/useCoachArtie' |
147 | 149 | import type { ObsidianFile } from './types'
|
148 | 150 |
|
149 | 151 | // Component imports
|
@@ -180,6 +182,7 @@ const { loadChatHistories, saveChatHistory, deleteAllChats } = useSupabase()
|
180 | 182 | const { isDark, systemPrefersDark } = useTheme()
|
181 | 183 | const aiChat = useAIChat()
|
182 | 184 | const openRouter = useOpenRouter()
|
| 185 | +const coachArtie = useCoachArtie() |
183 | 186 | const store = useStore()
|
184 | 187 | const obsidian = useObsidianFiles()
|
185 | 188 |
|
@@ -211,6 +214,7 @@ const chatContainerRef = ref<HTMLElement | null>(null)
|
211 | 214 | const messageInput = ref('')
|
212 | 215 | const isKeyboardShortcutsModalOpen = ref(false)
|
213 | 216 | const isClearChatHistoryModalOpen = ref(false)
|
| 217 | +const isDevelopmentMode = ref(import.meta.env.DEV) |
214 | 218 |
|
215 | 219 | // =========================================
|
216 | 220 | // Chat state
|
@@ -242,17 +246,44 @@ const preferences = useLocalStorage('preferences', {
|
242 | 246 | // Computed properties
|
243 | 247 | // =========================================
|
244 | 248 | const availableModels = computed(() => {
|
245 |
| - return openRouter.availableModels.value?.map(model => ({ |
| 249 | + // Create Coach Artie model |
| 250 | + const coachArtieModel = { |
| 251 | + id: 'coach-artie', |
| 252 | + name: '🤖 Coach Artie', |
| 253 | + description: 'Memory-enhanced local AI assistant', |
| 254 | + context_length: 100000, // Arbitrary large context size |
| 255 | + pricing: { |
| 256 | + prompt: '0', |
| 257 | + completion: '0' |
| 258 | + }, |
| 259 | + capabilities: { |
| 260 | + vision: false, |
| 261 | + tools: true, |
| 262 | + function_calling: true |
| 263 | + }, |
| 264 | + provider: 'local' |
| 265 | + }; |
| 266 | +
|
| 267 | + // Get OpenRouter models |
| 268 | + const openRouterModels = openRouter.availableModels.value?.map(model => ({ |
246 | 269 | ...model,
|
247 | 270 | name: model.name || model.id.split('/').pop() || '',
|
248 | 271 | description: model.description
|
249 |
| - })) || [] |
| 272 | + })) || []; |
| 273 | +
|
| 274 | + // Add Coach Artie at the top if it's available |
| 275 | + return coachArtie.isConnected.value |
| 276 | + ? [coachArtieModel, ...openRouterModels] |
| 277 | + : openRouterModels; |
250 | 278 | })
|
251 | 279 |
|
252 | 280 | const shouldShowWelcome = computed(() => {
|
253 | 281 | return messages.value.length === 0 && chatHistory.value.length === 0
|
254 | 282 | })
|
255 | 283 |
|
| 284 | +// Add a computed property to check if Coach Artie is the current model |
| 285 | +const isCoachArtieMode = computed(() => currentModel.value === 'coach-artie') |
| 286 | +
|
256 | 287 | // =========================================
|
257 | 288 | // Theme handling
|
258 | 289 | // =========================================
|
@@ -718,6 +749,31 @@ onMounted(async () => {
|
718 | 749 | logger.debug('Cleaning up IPC listeners')
|
719 | 750 | cleanupFns.forEach(cleanup => cleanup && cleanup())
|
720 | 751 | })
|
| 752 | +
|
| 753 | + // Sync the hasObsidianVault with the composable's computed property |
| 754 | + hasObsidianVault.value = obsidian.hasVault.value; |
| 755 | + console.log('Initial Obsidian state:'); |
| 756 | + console.log(' - hasVault from composable:', obsidian.hasVault.value); |
| 757 | + console.log(' - hasObsidianVault in App.vue:', hasObsidianVault.value); |
| 758 | +
|
| 759 | + // Ensure Obsidian vault is loaded with a slight delay to ensure store is ready |
| 760 | + setTimeout(async () => { |
| 761 | + await refreshObsidianVault(); |
| 762 | +
|
| 763 | + // Try a test search to verify functionality if we have a vault |
| 764 | + if (obsidian.hasVault.value) { |
| 765 | + try { |
| 766 | + isSearchingFiles.value = true; |
| 767 | + await obsidian.searchFiles('test'); |
| 768 | + console.log('Test search after init:', obsidian.searchResults.value?.length || 0, 'results'); |
| 769 | + obsidianSearchResults.value = obsidian.searchResults.value; |
| 770 | + } catch (err) { |
| 771 | + console.error('Test search failed:', err); |
| 772 | + } finally { |
| 773 | + isSearchingFiles.value = false; |
| 774 | + } |
| 775 | + } |
| 776 | + }, 1500); |
721 | 777 | })
|
722 | 778 |
|
723 | 779 | // Watch messages and scroll to bottom when they change
|
@@ -818,30 +874,74 @@ const renameChat = async (id: string, newTitle: string) => {
|
818 | 874 | }
|
819 | 875 | }
|
820 | 876 |
|
821 |
| -// Initialize obsidian integration |
| 877 | +// Watch for changes to the Obsidian vault status |
822 | 878 | watch(() => obsidian.hasVault.value, (hasVault) => {
|
823 |
| - hasObsidianVault.value = hasVault |
824 |
| - logger.debug('Obsidian vault detected:', hasVault) |
825 |
| -}) |
| 879 | + console.log('Obsidian vault status changed:', hasVault); |
| 880 | + console.log(' - Path:', obsidian.vaultPath.value); |
| 881 | + console.log(' - Path exists:', obsidian.pathExists.value); |
| 882 | +
|
| 883 | + // Synchronize the UI state with the composable state |
| 884 | + hasObsidianVault.value = hasVault; |
| 885 | +}, { immediate: true }) |
826 | 886 |
|
827 | 887 | // Watch for search query changes
|
828 | 888 | watch(() => searchQuery.value, async (query) => {
|
829 | 889 | console.log('App.vue - searchQuery changed:', query)
|
830 | 890 | console.log('App.vue - hasObsidianVault:', obsidian.hasVault.value)
|
| 891 | + console.log('App.vue - Obsidian vault path:', obsidian.vaultPath.value) |
831 | 892 |
|
832 | 893 | if (query && obsidian.hasVault.value) {
|
833 | 894 | isSearchingFiles.value = true
|
834 | 895 | console.log('App.vue - Before searchFiles call')
|
| 896 | +
|
| 897 | + // Add extra debug logging to trace the search process |
| 898 | + console.log('DEBUG - About to search files with query:', query) |
| 899 | + console.log('DEBUG - Current vault path:', obsidian.vaultPath.value) |
| 900 | + console.log('DEBUG - Is path valid:', !!obsidian.vaultPath.value && obsidian.vaultPath.value.length > 0) |
| 901 | +
|
| 902 | + // Perform the search |
835 | 903 | await obsidian.searchFiles(query)
|
| 904 | +
|
| 905 | + // Log results for debugging |
836 | 906 | console.log('App.vue - After searchFiles call')
|
837 | 907 | console.log('App.vue - searchResults:', obsidian.searchResults.value)
|
| 908 | + console.log('DEBUG - Search results count:', obsidian.searchResults.value?.length || 0) |
| 909 | +
|
838 | 910 | obsidianSearchResults.value = obsidian.searchResults.value as ObsidianFile[]
|
839 | 911 | isSearchingFiles.value = false
|
840 | 912 | logger.debug('Obsidian search results:', obsidianSearchResults.value.length)
|
841 | 913 | } else {
|
842 | 914 | obsidianSearchResults.value = []
|
843 | 915 | }
|
844 | 916 | })
|
| 917 | +
|
| 918 | +// Watch for Coach Artie availability and select it when it becomes available |
| 919 | +watch(() => coachArtie.isConnected.value, (isConnected) => { |
| 920 | + if (isConnected) { |
| 921 | + logger.debug('Coach Artie is now available, selecting it as the current model'); |
| 922 | + aiChat.setModel('coach-artie'); |
| 923 | + } |
| 924 | +}) |
| 925 | +
|
| 926 | +// Add this method to the script section |
| 927 | +const refreshObsidianVault = async () => { |
| 928 | + console.log('🔄 Refreshing Obsidian vault path'); |
| 929 | + try { |
| 930 | + // First, reload the vault path from store |
| 931 | + await obsidian.loadVaultPath(); |
| 932 | +
|
| 933 | + // Update the UI flag - use the computed value instead of path existence |
| 934 | + hasObsidianVault.value = obsidian.hasVault.value; |
| 935 | +
|
| 936 | + console.log('🔄 Vault refresh complete:'); |
| 937 | + console.log(' - Path:', obsidian.vaultPath.value); |
| 938 | + console.log(' - Path exists:', obsidian.pathExists.value); |
| 939 | + console.log(' - Has vault:', obsidian.hasVault.value); |
| 940 | + console.log(' - UI state:', hasObsidianVault.value); |
| 941 | + } catch (err) { |
| 942 | + console.error('🔄 Failed to refresh vault path:', err); |
| 943 | + } |
| 944 | +}; |
845 | 945 | </script>
|
846 | 946 |
|
847 | 947 | <style scoped>
|
@@ -1033,4 +1133,13 @@ watch(() => searchQuery.value, async (query) => {
|
1033 | 1133 | max-width: none;
|
1034 | 1134 | }
|
1035 | 1135 | }
|
| 1136 | +
|
| 1137 | +.coach-artie-mode { |
| 1138 | + --coach-artie-accent: rgba(79, 70, 229, 0.1); |
| 1139 | + background-image: linear-gradient(to bottom right, var(--coach-artie-accent), transparent); |
| 1140 | +} |
| 1141 | +
|
| 1142 | +.dark .coach-artie-mode { |
| 1143 | + --coach-artie-accent: rgba(79, 70, 229, 0.05); |
| 1144 | +} |
1036 | 1145 | </style>
|
0 commit comments