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:
2026-06-19 00:59:38 +09:00
co-authored by Claude Sonnet 4.6
parent 903d34761c
commit 4a08ba29fa
34 changed files with 5051 additions and 160 deletions
+51 -12
View File
@@ -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,