feat(frontend): update chat composables and vite config
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -101,7 +101,8 @@ export const conversationApi = {
|
|||||||
handlers: ChatStreamHandlers = {},
|
handlers: ChatStreamHandlers = {},
|
||||||
) {
|
) {
|
||||||
const token = localStorage.getItem('access_token')
|
const token = localStorage.getItem('access_token')
|
||||||
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/conversations/chat/stream`, {
|
const baseURL = import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL
|
||||||
|
const response = await fetch(`${baseURL}/api/conversations/chat/stream`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -13,17 +13,53 @@ export function useClientTime() {
|
|||||||
const weatherSummary = ref('Weather unavailable')
|
const weatherSummary = ref('Weather unavailable')
|
||||||
const weatherCode = ref<number | null>(null)
|
const weatherCode = ref<number | null>(null)
|
||||||
let clientTimeTimer: ReturnType<typeof setInterval> | null = null
|
let clientTimeTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
const WEATHER_CACHE_KEY = 'jarvis:clientWeather'
|
||||||
|
|
||||||
// Load location from backend config
|
function loadWeatherCache() {
|
||||||
async function loadLocation() {
|
if (typeof window === 'undefined') return
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem(WEATHER_CACHE_KEY)
|
||||||
|
if (!raw) return
|
||||||
|
const parsed = JSON.parse(raw) as { city?: string; weather_summary?: string; weather_code?: number | null; cached_at?: number }
|
||||||
|
if (typeof parsed.city === 'string' && parsed.city.trim()) city.value = parsed.city
|
||||||
|
if (typeof parsed.weather_summary === 'string' && parsed.weather_summary.trim()) weatherSummary.value = parsed.weather_summary
|
||||||
|
if (typeof parsed.weather_code === 'number') weatherCode.value = parsed.weather_code
|
||||||
|
} catch {
|
||||||
|
// ignore cache parse errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveWeatherCache(payload: { city: string; weather_summary: string; weather_code: number | null }) {
|
||||||
|
if (typeof window === 'undefined') return
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(WEATHER_CACHE_KEY, JSON.stringify({ ...payload, cached_at: Date.now() }))
|
||||||
|
} catch {
|
||||||
|
// ignore storage errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read cache synchronously during setup to avoid initial render flicker (icon showing as X/na).
|
||||||
|
loadWeatherCache()
|
||||||
|
|
||||||
|
async function loadSystemConfig() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/system/config')
|
const response = await fetch('/api/system/config')
|
||||||
if (response.ok) {
|
if (!response.ok) throw new Error('system config request failed')
|
||||||
const config = await response.json()
|
const config = await response.json()
|
||||||
city.value = config.location || 'Location'
|
city.value = typeof config.location === 'string' && config.location.trim() ? config.location : 'Location'
|
||||||
}
|
weatherCode.value = typeof config.weather_code === 'number' ? config.weather_code : null
|
||||||
|
weatherSummary.value = typeof config.weather_summary === 'string' && config.weather_summary.trim()
|
||||||
|
? config.weather_summary
|
||||||
|
: 'Weather unavailable'
|
||||||
|
saveWeatherCache({ city: city.value, weather_summary: weatherSummary.value, weather_code: weatherCode.value })
|
||||||
} catch {
|
} catch {
|
||||||
city.value = import.meta.env.VITE_LOCATION || 'Location'
|
// If we already have cached weather on screen, keep it to avoid UI flicker (icon showing as "X"/na).
|
||||||
|
const hasExistingWeather = weatherCode.value !== null || (weatherSummary.value && weatherSummary.value !== 'Weather unavailable')
|
||||||
|
if (!hasExistingWeather) {
|
||||||
|
city.value = 'Location'
|
||||||
|
weatherCode.value = null
|
||||||
|
weatherSummary.value = 'Weather unavailable'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +76,27 @@ export function useClientTime() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function weatherCodeLabel(code: number | null | undefined) {
|
function weatherCodeLabel(code: number | null | undefined) {
|
||||||
|
// Backend may return wttr.in condition codes (e.g. 113/116/119...), normalize to user-friendly labels.
|
||||||
|
if (typeof code === 'number' && code > 99) {
|
||||||
|
if (code === 113) return 'Clear'
|
||||||
|
if (code === 116) return 'Partly Cloudy'
|
||||||
|
if (code === 119 || code === 122) return 'Overcast'
|
||||||
|
if (code === 143) return 'Fog'
|
||||||
|
if ([200, 386, 389].includes(code)) return 'Thunderstorm'
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
176, 263, 266, 281, 293, 296, 299, 302, 305, 308,
|
||||||
|
311, 314, 317, 350, 353, 356, 359, 362,
|
||||||
|
].includes(code)
|
||||||
|
) return 'Rain'
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
179, 182, 185, 227, 230, 323, 326, 329, 332, 335,
|
||||||
|
338, 368, 371, 374, 377, 392, 395,
|
||||||
|
].includes(code)
|
||||||
|
) return 'Snow'
|
||||||
|
return 'Weather'
|
||||||
|
}
|
||||||
if (code === 0) return 'Clear'
|
if (code === 0) return 'Clear'
|
||||||
if (code === 1 || code === 2) return 'Partly Cloudy'
|
if (code === 1 || code === 2) return 'Partly Cloudy'
|
||||||
if (code === 3) return 'Overcast'
|
if (code === 3) return 'Overcast'
|
||||||
@@ -48,11 +105,34 @@ export function useClientTime() {
|
|||||||
if ([61, 63, 65, 66, 67, 80, 81, 82].includes(code ?? -1)) return 'Rain'
|
if ([61, 63, 65, 66, 67, 80, 81, 82].includes(code ?? -1)) return 'Rain'
|
||||||
if ([71, 73, 75, 77, 85, 86].includes(code ?? -1)) return 'Snow'
|
if ([71, 73, 75, 77, 85, 86].includes(code ?? -1)) return 'Snow'
|
||||||
if ([95, 96, 99].includes(code ?? -1)) return 'Thunderstorm'
|
if ([95, 96, 99].includes(code ?? -1)) return 'Thunderstorm'
|
||||||
return 'Weather'
|
return 'Weather unavailable'
|
||||||
}
|
}
|
||||||
|
|
||||||
const weatherIcon = computed(() => {
|
const weatherIcon = computed(() => {
|
||||||
const code = weatherCode.value
|
const code = weatherCode.value
|
||||||
|
if (code === null || code === undefined) return ''
|
||||||
|
// Support wttr.in weather codes (commonly 113/116/119/122/143/...).
|
||||||
|
if (typeof code === 'number' && code > 99) {
|
||||||
|
if (code === 113) return 'wi-day-sunny'
|
||||||
|
if (code === 116) return 'wi-day-cloudy'
|
||||||
|
if (code === 119) return 'wi-cloudy'
|
||||||
|
if (code === 122) return 'wi-cloudy'
|
||||||
|
if (code === 143) return 'wi-fog'
|
||||||
|
if ([200, 386, 389].includes(code)) return 'wi-thunderstorm'
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
176, 263, 266, 281, 293, 296, 299, 302, 305, 308,
|
||||||
|
311, 314, 317, 350, 353, 356, 359, 362,
|
||||||
|
].includes(code)
|
||||||
|
) return 'wi-rain'
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
179, 182, 185, 227, 230, 323, 326, 329, 332, 335,
|
||||||
|
338, 368, 371, 374, 377, 392, 395,
|
||||||
|
].includes(code)
|
||||||
|
) return 'wi-snow'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
if (code === 0) return 'wi-day-sunny'
|
if (code === 0) return 'wi-day-sunny'
|
||||||
if (code === 1) return 'wi-day-cloudy'
|
if (code === 1) return 'wi-day-cloudy'
|
||||||
if (code === 2) return 'wi-day-cloudy-gusts'
|
if (code === 2) return 'wi-day-cloudy-gusts'
|
||||||
@@ -65,68 +145,13 @@ export function useClientTime() {
|
|||||||
if ([66, 67, 81, 82].includes(code ?? -1)) return 'wi-rain'
|
if ([66, 67, 81, 82].includes(code ?? -1)) return 'wi-rain'
|
||||||
if ([71, 73, 75, 77, 85, 86].includes(code ?? -1)) return 'wi-snow'
|
if ([71, 73, 75, 77, 85, 86].includes(code ?? -1)) return 'wi-snow'
|
||||||
if ([95, 96, 99].includes(code ?? -1)) return 'wi-thunderstorm'
|
if ([95, 96, 99].includes(code ?? -1)) return 'wi-thunderstorm'
|
||||||
return 'wi-day-sunny'
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fallback: directly load weather with default location (Beijing)
|
onMounted(async () => {
|
||||||
async function loadWeatherByIP() {
|
|
||||||
console.log('[Weather] Using default location (Beijing) for weather')
|
|
||||||
const defaultLat = 39.9042
|
|
||||||
const defaultLon = 116.4074
|
|
||||||
await loadWeather(defaultLat, defaultLon)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadWeather(latitude: number, longitude: number) {
|
|
||||||
console.log('[Weather] Loading weather for:', latitude, longitude)
|
|
||||||
try {
|
|
||||||
// Fetch weather data from Open-Meteo
|
|
||||||
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,weather_code&timezone=auto`
|
|
||||||
console.log('[Weather] Fetching:', url)
|
|
||||||
const weatherResp = await fetch(url)
|
|
||||||
console.log('[Weather] Response status:', weatherResp.status)
|
|
||||||
if (!weatherResp.ok) throw new Error('weather request failed')
|
|
||||||
const weatherData = await weatherResp.json()
|
|
||||||
console.log('[Weather] Response data:', JSON.stringify(weatherData))
|
|
||||||
const current = weatherData.current ?? {}
|
|
||||||
weatherCode.value = typeof current.weather_code === 'number' ? current.weather_code : null
|
|
||||||
const temp = typeof current.temperature_2m === 'number' ? `${Math.round(current.temperature_2m)}°C` : '--'
|
|
||||||
weatherSummary.value = `${weatherCodeLabel(current.weather_code)} ${temp}`
|
|
||||||
console.log('[Weather] Updated summary:', weatherSummary.value)
|
|
||||||
|
|
||||||
// Only fetch city name if not already set by config
|
|
||||||
if (city.value === 'Location') {
|
|
||||||
try {
|
|
||||||
const geoResp = await fetch(
|
|
||||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&zoom=10`,
|
|
||||||
)
|
|
||||||
if (geoResp.ok) {
|
|
||||||
const geoData = await geoResp.json()
|
|
||||||
city.value = geoData.address?.city ?? geoData.address?.town ?? geoData.address?.county ?? geoData.display_name?.split(',')[0] ?? 'Location'
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
city.value = 'Location'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('[Weather] API failed (keeping previous value):', err)
|
|
||||||
// Don't overwrite existing value on error - keep the default we set earlier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
updateClientTime()
|
updateClientTime()
|
||||||
clientTimeTimer = setInterval(updateClientTime, 1000)
|
clientTimeTimer = setInterval(updateClientTime, 1000)
|
||||||
void loadLocation()
|
await loadSystemConfig()
|
||||||
|
|
||||||
// Set default weather immediately
|
|
||||||
weatherCode.value = 0
|
|
||||||
weatherSummary.value = 'Clear 25°C'
|
|
||||||
city.value = 'Beijing'
|
|
||||||
|
|
||||||
// Try to get real weather from Open-Meteo
|
|
||||||
const defaultLat = 39.9042
|
|
||||||
const defaultLon = 116.4074
|
|
||||||
loadWeather(defaultLat, defaultLon)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -134,7 +159,15 @@ export function useClientTime() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clientTime, city, weatherSummary, weatherCode, weatherIcon,
|
clientTime,
|
||||||
updateClientTime, formatClientDate, formatClientClock, weatherCodeLabel, loadWeather
|
city,
|
||||||
|
weatherSummary,
|
||||||
|
weatherCode,
|
||||||
|
weatherIcon,
|
||||||
|
updateClientTime,
|
||||||
|
formatClientDate,
|
||||||
|
formatClientClock,
|
||||||
|
weatherCodeLabel,
|
||||||
|
loadSystemConfig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { computed, onMounted, ref, watch, toRef } from 'vue'
|
import { computed, onMounted, ref, watch, type Ref } from 'vue'
|
||||||
import { CornerDownLeft, Database, Sparkles, Sun, ListTodo } from 'lucide-vue-next'
|
import { CornerDownLeft, Database, Sparkles, Sun, ListTodo } from 'lucide-vue-next'
|
||||||
import { scheduleCenterApi, type ScheduleCenterDateResponse, type ScheduleCenterDaySummary } from '@/api/scheduleCenter'
|
import { scheduleCenterApi, type ScheduleCenterDateResponse, type ScheduleCenterDaySummary } from '@/api/scheduleCenter'
|
||||||
import type { Conversation } from '@/api/conversation'
|
import type { Conversation } from '@/api/conversation'
|
||||||
@@ -18,20 +18,24 @@ export const sidebarCollapsedModules = [
|
|||||||
{ id: 'review', label: '复盘', icon: CornerDownLeft },
|
{ id: 'review', label: '复盘', icon: CornerDownLeft },
|
||||||
]
|
]
|
||||||
|
|
||||||
function formatDateKey(date: Date) {
|
export function formatDateKey(date: Date) {
|
||||||
const year = date.getUTCFullYear()
|
const year = date.getFullYear()
|
||||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0')
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
const day = String(date.getUTCDate()).padStart(2, '0')
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
return `${year}-${month}-${day}`
|
return `${year}-${month}-${day}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatMonthKey(date: Date) {
|
function formatMonthKey(date: Date) {
|
||||||
const year = date.getUTCFullYear()
|
const year = date.getFullYear()
|
||||||
const month = String(date.getUTCMonth() + 1).padStart(2, '0')
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
return `${year}-${month}`
|
return `${year}-${month}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSidebarPlan(clientTimeRef: { value: Date }, loadDailyDigestFn: () => void, conversationsRef: Conversation[] = []) {
|
export function useSidebarPlan(
|
||||||
|
clientTimeRef: { value: Date },
|
||||||
|
loadDailyDigestFn: () => void,
|
||||||
|
conversationsRef: Ref<Conversation[]> | Conversation[] = [],
|
||||||
|
) {
|
||||||
const todayPlanDetail = ref<ScheduleCenterDateResponse | null>(null)
|
const todayPlanDetail = ref<ScheduleCenterDateResponse | null>(null)
|
||||||
const monthPlanDays = ref<ScheduleCenterDaySummary[]>([])
|
const monthPlanDays = ref<ScheduleCenterDaySummary[]>([])
|
||||||
const selectedDate = ref<string | null>(null)
|
const selectedDate = ref<string | null>(null)
|
||||||
@@ -41,9 +45,7 @@ export function useSidebarPlan(clientTimeRef: { value: Date }, loadDailyDigestFn
|
|||||||
const map = new Map<string, boolean>()
|
const map = new Map<string, boolean>()
|
||||||
const conversations = Array.isArray(conversationsRef) ? conversationsRef : (conversationsRef.value ?? [])
|
const conversations = Array.isArray(conversationsRef) ? conversationsRef : (conversationsRef.value ?? [])
|
||||||
conversations.forEach((conv) => {
|
conversations.forEach((conv) => {
|
||||||
const date = new Date(conv.updated_at)
|
map.set(formatDateKey(new Date(conv.updated_at)), true)
|
||||||
const dateKey = formatDateKey(date)
|
|
||||||
map.set(dateKey, true)
|
|
||||||
})
|
})
|
||||||
return map
|
return map
|
||||||
})
|
})
|
||||||
@@ -52,22 +54,22 @@ export function useSidebarPlan(clientTimeRef: { value: Date }, loadDailyDigestFn
|
|||||||
const monthPlanSummaryMap = computed(() => new Map(monthPlanDays.value.map((item) => [item.date, item])))
|
const monthPlanSummaryMap = computed(() => new Map(monthPlanDays.value.map((item) => [item.date, item])))
|
||||||
|
|
||||||
const calendarCells = computed(() => {
|
const calendarCells = computed(() => {
|
||||||
const year = clientTimeRef.value.getUTCFullYear()
|
const year = clientTimeRef.value.getFullYear()
|
||||||
const month = clientTimeRef.value.getUTCMonth()
|
const month = clientTimeRef.value.getMonth()
|
||||||
const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate()
|
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
||||||
const firstDayOffset = (new Date(Date.UTC(year, month, 1)).getUTCDay() + 6) % 7
|
const firstDayOffset = (new Date(year, month, 1).getDay() + 6) % 7
|
||||||
const today = clientTimeRef.value.getUTCDate()
|
const todayKey = todayDateKey.value
|
||||||
const cells: Array<{ key: string; value: number | null; active: boolean; busy: boolean; selected: boolean; hasConversation: boolean }> = []
|
const cells: Array<{ key: string; value: number | null; active: boolean; busy: boolean; selected: boolean; hasConversation: boolean }> = []
|
||||||
for (let index = 0; index < firstDayOffset; index += 1) {
|
for (let index = 0; index < firstDayOffset; index += 1) {
|
||||||
cells.push({ key: `blank-start-${index}`, value: null, active: false, busy: false, selected: false, hasConversation: false })
|
cells.push({ key: `blank-start-${index}`, value: null, active: false, busy: false, selected: false, hasConversation: false })
|
||||||
}
|
}
|
||||||
for (let day = 1; day <= daysInMonth; day += 1) {
|
for (let day = 1; day <= daysInMonth; day += 1) {
|
||||||
const monthDate = new Date(Date.UTC(year, month, day))
|
const monthDate = new Date(year, month, day)
|
||||||
const dateKey = formatDateKey(monthDate)
|
const dateKey = formatDateKey(monthDate)
|
||||||
const summary = monthPlanSummaryMap.value.get(dateKey)
|
const summary = monthPlanSummaryMap.value.get(dateKey)
|
||||||
const busy = Boolean(summary && (summary.todo_total + summary.task_due_total + summary.goal_total + summary.reminder_total) > 0)
|
const busy = Boolean(summary && (summary.todo_total + summary.task_due_total + summary.goal_total + summary.reminder_total) > 0)
|
||||||
const hasConv = conversationDateMap.value.get(dateKey) || false
|
const hasConv = conversationDateMap.value.get(dateKey) || false
|
||||||
cells.push({ key: dateKey, value: day, active: day === today, busy, selected: dateKey === selectedDate.value, hasConversation: hasConv })
|
cells.push({ key: dateKey, value: day, active: dateKey === todayKey, busy, selected: dateKey === selectedDate.value, hasConversation: hasConv })
|
||||||
}
|
}
|
||||||
while (cells.length % 7 !== 0) {
|
while (cells.length % 7 !== 0) {
|
||||||
cells.push({ key: `blank-end-${cells.length}`, value: null, active: false, busy: false, selected: false, hasConversation: false })
|
cells.push({ key: `blank-end-${cells.length}`, value: null, active: false, busy: false, selected: false, hasConversation: false })
|
||||||
@@ -75,8 +77,8 @@ export function useSidebarPlan(clientTimeRef: { value: Date }, loadDailyDigestFn
|
|||||||
return cells
|
return cells
|
||||||
})
|
})
|
||||||
|
|
||||||
const calendarYear = computed(() => clientTimeRef.value.getUTCFullYear())
|
const calendarYear = computed(() => clientTimeRef.value.getFullYear())
|
||||||
const calendarMonth = computed(() => clientTimeRef.value.getUTCMonth() + 1)
|
const calendarMonth = computed(() => clientTimeRef.value.getMonth() + 1)
|
||||||
|
|
||||||
const todayPlanCounters = computed(() => {
|
const todayPlanCounters = computed(() => {
|
||||||
const detail = todayPlanDetail.value
|
const detail = todayPlanDetail.value
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, toRef } from 'vue'
|
||||||
import {
|
import {
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Send,
|
Send,
|
||||||
@@ -25,7 +25,7 @@ import { useChatView } from '@/pages/chat/composables/useChatView'
|
|||||||
import { useKnowledgeView } from '@/pages/knowledge/composables/useKnowledgeView'
|
import { useKnowledgeView } from '@/pages/knowledge/composables/useKnowledgeView'
|
||||||
import { useDailyDigest } from '@/pages/chat/composables/useDailyDigest'
|
import { useDailyDigest } from '@/pages/chat/composables/useDailyDigest'
|
||||||
import { useClientTime, formatNetworkRate } from '@/pages/chat/composables/useClientTime'
|
import { useClientTime, formatNetworkRate } from '@/pages/chat/composables/useClientTime'
|
||||||
import { useSidebarPlan } from '@/pages/chat/composables/useSidebarPlan'
|
import { useSidebarPlan, formatDateKey } from '@/pages/chat/composables/useSidebarPlan'
|
||||||
|
|
||||||
// --- Chat view (core messaging logic) ---
|
// --- Chat view (core messaging logic) ---
|
||||||
const {
|
const {
|
||||||
@@ -79,7 +79,7 @@ const {
|
|||||||
sidebarFocusItems, sidebarReviewAchievements, sidebarReviewReflections,
|
sidebarFocusItems, sidebarReviewAchievements, sidebarReviewReflections,
|
||||||
sidebarFeedItems, topbarFeedItems, sidebarCollapsedModules,
|
sidebarFeedItems, topbarFeedItems, sidebarCollapsedModules,
|
||||||
selectedDate, selectCalendarDate
|
selectedDate, selectCalendarDate
|
||||||
} = useSidebarPlan(clientTime, loadDailyDigest, store.conversations)
|
} = useSidebarPlan(clientTime, loadDailyDigest, toRef(store, 'conversations'))
|
||||||
|
|
||||||
// --- Local UI state ---
|
// --- Local UI state ---
|
||||||
const sidebarCollapsed = ref(false)
|
const sidebarCollapsed = ref(false)
|
||||||
@@ -118,27 +118,16 @@ function handleOpenPreview(doc: any) { previewDoc.value = doc }
|
|||||||
function handleCalendarDateSelect(dateKey: string) {
|
function handleCalendarDateSelect(dateKey: string) {
|
||||||
selectCalendarDate(dateKey)
|
selectCalendarDate(dateKey)
|
||||||
|
|
||||||
// Reload conversations to get latest data
|
|
||||||
loadConversations().then(() => {
|
loadConversations().then(() => {
|
||||||
// Find conversation that matches the selected date (by updated_at)
|
const conversations = store.conversations
|
||||||
// Use UTC to avoid timezone issues
|
const conversation = conversations.find((conv: { id: string; updated_at: string }) => formatDateKey(new Date(conv.updated_at)) === dateKey)
|
||||||
const [year, month, day] = dateKey.split('-').map(Number)
|
|
||||||
const targetDateStart = new Date(Date.UTC(year, month - 1, day))
|
|
||||||
const targetDateEnd = new Date(Date.UTC(year, month - 1, day + 1))
|
|
||||||
|
|
||||||
// Find conversation that falls on the selected date
|
|
||||||
const conversations = store.conversations.value ?? store.conversations
|
|
||||||
const conversation = conversations.find((conv) => {
|
|
||||||
const convDate = new Date(conv.updated_at)
|
|
||||||
return convDate >= targetDateStart && convDate < targetDateEnd
|
|
||||||
})
|
|
||||||
|
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
selectConversation(conversation.id)
|
selectConversation(conversation.id)
|
||||||
} else {
|
return
|
||||||
// No conversation for this date, create a new one
|
|
||||||
newConversation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newConversation()
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error('[Calendar] Error loading conversations:', err)
|
console.error('[Calendar] Error loading conversations:', err)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,10 +1,44 @@
|
|||||||
import { defineConfig, loadEnv } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, __dirname, '')
|
const env = loadEnv(mode, __dirname, '')
|
||||||
|
|
||||||
|
function parseDotenvFile(filePath: string) {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(filePath)) return {}
|
||||||
|
const text = fs.readFileSync(filePath, 'utf-8')
|
||||||
|
const result: Record<string, string> = {}
|
||||||
|
for (const rawLine of text.split(/\r?\n/)) {
|
||||||
|
const line = rawLine.trim()
|
||||||
|
if (!line || line.startsWith('#')) continue
|
||||||
|
const eq = line.indexOf('=')
|
||||||
|
if (eq <= 0) continue
|
||||||
|
const key = line.slice(0, eq).trim()
|
||||||
|
let value = line.slice(eq + 1).trim()
|
||||||
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
||||||
|
value = value.slice(1, -1)
|
||||||
|
}
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vite only loads env files under `frontend/` by default.
|
||||||
|
// Many Jarvis setups keep HOST/PORT in repo root `.env`, so we read it explicitly as a fallback.
|
||||||
|
const rootEnvPath = path.resolve(__dirname, '../.env')
|
||||||
|
const rootEnv = parseDotenvFile(rootEnvPath)
|
||||||
|
const rootHost = (rootEnv.HOST || '127.0.0.1').trim()
|
||||||
|
const rootPort = (rootEnv.PORT || '').trim()
|
||||||
|
const rootApi = (rootEnv.VITE_API_URL || (rootPort ? `http://${rootHost}:${rootPort}` : '')).trim()
|
||||||
|
|
||||||
|
const apiTarget = env.VITE_API_URL || rootApi || 'http://localhost:8000'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
resolve: {
|
resolve: {
|
||||||
@@ -15,7 +49,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: env.VITE_API_URL,
|
target: apiTarget,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user