Add system memory monitoring, Bedrock support, and Discord integrations
- Add CPU/GPU memory monitoring endpoint and frontend widget - Add Amazon Bedrock model support (API key / inference profiles) plus model selector entries - Add Discord weather/laundry notification job (JMA forecast + LLM-generated message) - Add Discord bot that replies to mentions via the configured LLM, with weather-aware context - Add scheduled "useless tips" job with separate webhook and quiet-hours skip - Switch FastAPI startup to lifespan handler, log viewer, markdown rendering, and toast UI additions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+51
-12
@@ -1,31 +1,61 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { fetchSessions, deleteSession, streamChat, type SessionInfo } from '../api/client'
|
||||
import { fetchSessions, fetchMessages, deleteSession, streamChat, type SessionInfo } from '../api/client'
|
||||
|
||||
export interface Message {
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
pending?: boolean
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
export interface Toast {
|
||||
id: number
|
||||
message: string
|
||||
}
|
||||
|
||||
let toastSeq = 0
|
||||
|
||||
export const useChatStore = defineStore('chat', () => {
|
||||
const sessions = ref<SessionInfo[]>([])
|
||||
const currentSessionId = ref<string | null>(null)
|
||||
const messages = ref<Message[]>([])
|
||||
const isStreaming = ref(false)
|
||||
const selectedModel = ref('gpt-4o')
|
||||
const selectedModel = ref('gpt-5.4')
|
||||
const toasts = ref<Toast[]>([])
|
||||
|
||||
const currentSession = computed(() =>
|
||||
sessions.value.find((s) => s.id === currentSessionId.value) ?? null,
|
||||
)
|
||||
|
||||
function pushToast(message: string, durationMs = 5000) {
|
||||
const id = ++toastSeq
|
||||
toasts.value.push({ id, message })
|
||||
setTimeout(() => {
|
||||
toasts.value = toasts.value.filter((t) => t.id !== id)
|
||||
}, durationMs)
|
||||
}
|
||||
|
||||
function dismissToast(id: number) {
|
||||
toasts.value = toasts.value.filter((t) => t.id !== id)
|
||||
}
|
||||
|
||||
async function loadSessions() {
|
||||
sessions.value = await fetchSessions()
|
||||
}
|
||||
|
||||
function selectSession(sessionId: string | null) {
|
||||
async function selectSession(sessionId: string | null) {
|
||||
currentSessionId.value = sessionId
|
||||
messages.value = []
|
||||
if (!sessionId) return
|
||||
try {
|
||||
const records = await fetchMessages(sessionId)
|
||||
messages.value = records
|
||||
.filter((r) => r.role === 'user' || r.role === 'assistant')
|
||||
.map((r) => ({ role: r.role as 'user' | 'assistant', content: r.content }))
|
||||
} catch (e) {
|
||||
pushToast(`メッセージ取得失敗: ${e instanceof Error ? e.message : e}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function removeSession(sessionId: string) {
|
||||
@@ -41,8 +71,8 @@ export const useChatStore = defineStore('chat', () => {
|
||||
if (isStreaming.value || !text.trim()) return
|
||||
|
||||
messages.value.push({ role: 'user', content: text })
|
||||
const assistantMsg: Message = { role: 'assistant', content: '', pending: true }
|
||||
messages.value.push(assistantMsg)
|
||||
messages.value.push({ role: 'assistant', content: '', pending: true })
|
||||
const msgIdx = messages.value.length - 1 // reactive proxy 経由で更新するためインデックスを保持
|
||||
isStreaming.value = true
|
||||
|
||||
streamChat(
|
||||
@@ -52,18 +82,24 @@ export const useChatStore = defineStore('chat', () => {
|
||||
(chunk) => {
|
||||
if (chunk.type === 'session_id' && chunk.value) {
|
||||
currentSessionId.value = chunk.value
|
||||
loadSessions()
|
||||
loadSessions().catch((e) => pushToast(`セッション更新失敗: ${e.message}`))
|
||||
} else if (chunk.type === 'chunk' && chunk.value) {
|
||||
assistantMsg.content += chunk.value
|
||||
messages.value[msgIdx].content += chunk.value
|
||||
} else if (chunk.type === 'done') {
|
||||
assistantMsg.pending = false
|
||||
messages.value[msgIdx].pending = false
|
||||
isStreaming.value = false
|
||||
loadSessions().catch((e) => pushToast(`セッション更新失敗: ${e.message}`))
|
||||
} else if (chunk.type === 'error') {
|
||||
messages.value[msgIdx].content = chunk.value ?? 'バックエンドでエラーが発生しました。'
|
||||
messages.value[msgIdx].pending = false
|
||||
messages.value[msgIdx].error = true
|
||||
isStreaming.value = false
|
||||
loadSessions()
|
||||
}
|
||||
},
|
||||
(_err) => {
|
||||
assistantMsg.content = 'エラーが発生しました。'
|
||||
assistantMsg.pending = false
|
||||
(err) => {
|
||||
messages.value[msgIdx].content = err.message
|
||||
messages.value[msgIdx].pending = false
|
||||
messages.value[msgIdx].error = true
|
||||
isStreaming.value = false
|
||||
},
|
||||
)
|
||||
@@ -75,7 +111,10 @@ export const useChatStore = defineStore('chat', () => {
|
||||
messages,
|
||||
isStreaming,
|
||||
selectedModel,
|
||||
toasts,
|
||||
currentSession,
|
||||
pushToast,
|
||||
dismissToast,
|
||||
loadSessions,
|
||||
selectSession,
|
||||
removeSession,
|
||||
|
||||
Reference in New Issue
Block a user