Compare commits

...

10 Commits

11 changed files with 87 additions and 13 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# Portal Development Environment # Portal Development Environment
VITE_APP_TITLE=Momentry Portal (Development) VITE_APP_TITLE=Momentry Portal (Development)
VITE_API_BASE_URL=http://127.0.0.1:3002 VITE_API_BASE_URL=http://192.168.110.201:3002
VITE_API_KEY=muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69 VITE_API_KEY=muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69
-2
View File
@@ -6,8 +6,6 @@ dist/
src-tauri/target/ src-tauri/target/
src-tauri/gen/schemas/ src-tauri/gen/schemas/
# Tauri icons (generated)
src-tauri/icons/
# Environment # Environment
.env .env
+16
View File
@@ -1,5 +1,9 @@
# Momentry Portal — AGENTS.md # Momentry Portal — AGENTS.md
## 其他專案(M4mini
- **markbase** — M4mini 上正在開發的重要專案
## 開發指令 ## 開發指令
| 用途 | 指令 | | 用途 | 指令 |
@@ -31,6 +35,18 @@
- API 設定存於 `localStorage('portal_config')` - API 設定存於 `localStorage('portal_config')`
- 401 回應會自動清除登入狀態並跳回 `/login` - 401 回應會自動清除登入狀態並跳回 `/login`
## 跨機器推送與測試(M4mini → M5Max128
Gitea 伺服器在 `m5max128.local:3000`admin / `AccusysTest!`)。開發在 M4mini 進行,測試在 M5Max128
```bash
# 一鍵推送 + 部署 + 建置
./deploy.sh [commit message]
# 或手動
git commit -am "..." && git push
ssh accusys@m5max128.local "cd ~/momentry_portal && git pull && npm run build"
```
## 注意事項 ## 注意事項
- `tsconfig.json` 啟用 `strict` + `noUnusedLocals` + `noUnusedParameters` — 未使用的變數/參數會造成 `vue-tsc` 建置錯誤 - `tsconfig.json` 啟用 `strict` + `noUnusedLocals` + `noUnusedParameters` — 未使用的變數/參數會造成 `vue-tsc` 建置錯誤
Executable
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
REMOTE_USER="accusys"
REMOTE_HOST="m5max128.local"
REMOTE_DIR="~/momentry_portal"
if ! git diff --quiet --cached; then
echo ":: Unstaged changes detected. Stage or stash them first."
echo " git add -A && git stash"
exit 1
fi
if [ -n "$(git status --porcelain)" ]; then
echo ":: Uncommitted changes found."
if [ $# -ge 1 ]; then
git add -A && git commit -m "$*"
else
git add -A && git commit -m "deploy: $(date '+%Y-%m-%d %H:%M')"
fi
fi
echo ":: Pushing to Gitea..."
git push
echo ":: Deploying to ${REMOTE_HOST}..."
ssh "${REMOTE_USER}@${REMOTE_HOST}" \
"export PATH=\"\$HOME/.local/bin:\$PATH\" && eval \"\$(fnm env --use-on-cd)\" && \
cd ${REMOTE_DIR} && \
git pull && \
npm run build"
echo ":: Done"
+1
View File
@@ -2,6 +2,7 @@
"name": "momentry-portal", "name": "momentry-portal",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

+2 -3
View File
@@ -1,6 +1,5 @@
use crate::config::get_config; use crate::config::get_config;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct SearchRequest { pub struct SearchRequest {
@@ -54,7 +53,7 @@ pub async fn search_videos(
mode: Some(search_mode.clone()), mode: Some(search_mode.clone()),
}; };
let url = format!("{}/api/v1/search", config.api_base_url); let url = format!("{}/api/v1/search/universal", config.api_base_url);
let response = client let response = client
.post(&url) .post(&url)
@@ -113,7 +112,7 @@ pub async fn search_chunks(query: String, uuid: Option<String>) -> Result<Search
let client = reqwest::Client::new(); let client = reqwest::Client::new();
// Backend expects uuid in the body, not query params // Backend expects uuid in the body, not query params
let url = format!("{}/api/v1/search", config.api_base_url); let url = format!("{}/api/v1/search/universal", config.api_base_url);
let mut request_body = serde_json::json!({ let mut request_body = serde_json::json!({
"query": query, "query": query,
+1 -1
View File
@@ -50,7 +50,7 @@ pub async fn translate_text(
println!("[Translate] Sending to llama.cpp server..."); println!("[Translate] Sending to llama.cpp server...");
let response = client let response = client
.post("http://127.0.0.1:8081/v1/chat/completions") .post("http://127.0.0.1:8082/v1/chat/completions")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.json(&payload) .json(&payload)
.send() .send()
-1
View File
@@ -127,7 +127,6 @@ function isTauri(): boolean {
function handleSessionExpired() { function handleSessionExpired() {
console.warn("Session expired or connection error, redirecting to login..."); console.warn("Session expired or connection error, redirecting to login...");
localStorage.removeItem('momentry_user'); localStorage.removeItem('momentry_user');
localStorage.removeItem('portal_config');
localStorage.removeItem('momentry_api_key'); localStorage.removeItem('momentry_api_key');
if (window.location.pathname !== '/login') { if (window.location.pathname !== '/login') {
window.location.href = '/login'; window.location.href = '/login';
+23 -3
View File
@@ -115,9 +115,29 @@ const loadDetail = async () => {
detail.value = result detail.value = result
profile.value = result.profile || {} profile.value = result.profile || {}
videos.value = result.videos || [] videos.value = result.videos || []
} catch (error) { } catch {
console.error('Failed to load identity detail:', error) // Fallback: some API servers crash on /api/v1/identity/:uuid
alert('載入失敗: ' + error) try {
const config = getCurrentConfig()
const result = await httpFetch<any>(`${config.api_base_url}/api/v1/identities?uuid=${identityId.value}&page_size=1`)
const identity = (result.identities || [])[0]
if (identity) {
detail.value = identity
const meta = identity.metadata || {}
profile.value = {
name: identity.name,
character_name: meta.tmdb_character,
aliases: meta.tmdb_aliases,
gender: meta.tmdb_gender === 1 ? 'Female' : meta.tmdb_gender === 2 ? 'Male' : undefined,
age: meta.tmdb_birthday ? `${new Date().getFullYear() - new Date(meta.tmdb_birthday).getFullYear()} yrs` : undefined,
}
videos.value = []
} else {
throw new Error('Identity not found')
}
} catch (fallbackError) {
console.error('Failed to load identity detail:', fallbackError)
}
} finally { } finally {
loading.value = false loading.value = false
} }
+10 -2
View File
@@ -143,21 +143,27 @@ const customUrl = ref('')
const selectedUrl = ref('') const selectedUrl = ref('')
function initSelectedServer() { function initSelectedServer() {
// Reset api_key from env defaults to clear any stale key
localStorage.removeItem('portal_config')
const config = getCurrentConfig() const config = getCurrentConfig()
const currentUrl = config.api_base_url const currentUrl = config.api_base_url
const matched = serverPresets.find(s => s.url === currentUrl) const matched = serverPresets.find(s => s.url === currentUrl)
if (matched) { if (matched) {
selectedUrl.value = matched.url selectedUrl.value = matched.url
showCustomUrl.value = false showCustomUrl.value = false
saveConfig({ ...config, api_base_url: matched.url })
} else { } else {
selectedUrl.value = currentUrl selectedUrl.value = currentUrl
customUrl.value = currentUrl customUrl.value = currentUrl
showCustomUrl.value = true showCustomUrl.value = true
saveConfig({ ...config })
} }
} }
function selectServer(srv: ServerPreset) { function selectServer(srv: ServerPreset) {
selectedUrl.value = srv.url selectedUrl.value = srv.url
// Reset api_key from env to avoid using a stale key
localStorage.removeItem('portal_config')
const config = getCurrentConfig() const config = getCurrentConfig()
saveConfig({ ...config, api_base_url: srv.url }) saveConfig({ ...config, api_base_url: srv.url })
} }
@@ -178,6 +184,8 @@ function toggleCustom() {
function onCustomUrlInput() { function onCustomUrlInput() {
selectedUrl.value = customUrl.value selectedUrl.value = customUrl.value
// Reset api_key from env
localStorage.removeItem('portal_config')
const config = getCurrentConfig() const config = getCurrentConfig()
saveConfig({ ...config, api_base_url: customUrl.value }) saveConfig({ ...config, api_base_url: customUrl.value })
} }
@@ -199,6 +207,7 @@ const handleLogin = async () => {
let apiUrl = selectedUrl.value let apiUrl = selectedUrl.value
if (showCustomUrl.value && customUrl.value) { if (showCustomUrl.value && customUrl.value) {
apiUrl = customUrl.value apiUrl = customUrl.value
localStorage.removeItem('portal_config')
const config = getCurrentConfig() const config = getCurrentConfig()
saveConfig({ ...config, api_base_url: customUrl.value }) saveConfig({ ...config, api_base_url: customUrl.value })
} }
@@ -217,8 +226,7 @@ const handleLogin = async () => {
if (data.success) { if (data.success) {
localStorage.setItem('momentry_user', JSON.stringify(data.user)) localStorage.setItem('momentry_user', JSON.stringify(data.user))
localStorage.setItem('momentry_api_key', data.api_key) localStorage.setItem('momentry_api_key', data.api_key)
const config = getCurrentConfig() // Keep the existing api_key from config/env (login response key may be a placeholder)
saveConfig({ ...config, api_key: data.api_key })
const redirect = (route.query.redirect as string) || '/home' const redirect = (route.query.redirect as string) || '/home'
router.push(redirect) router.push(redirect)
} else { } else {