diff --git a/Dockerfile b/Dockerfile index fa3be94a..00060987 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ FROM python:3.11-slim ENV DEBIAN_FRONTEND=noninteractive \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ - VITE_API_BASE_URL=/api \ + VITE_API_BASE_URL="" \ HF_HUB_OFFLINE=0 \ SENTENCE_TRANSFORMERS_HOME=/root/.cache/huggingface diff --git a/backend/app/services/graphiti_service.py b/backend/app/services/graphiti_service.py index ef8783f2..ee5d1f6c 100644 --- a/backend/app/services/graphiti_service.py +++ b/backend/app/services/graphiti_service.py @@ -316,9 +316,13 @@ class MinimaxLLMClient(LLMClient): def _strip_reasoning_and_fence(s: str) -> str: """Remove ... reasoning blocks and markdown code fences.""" s = s.strip() - # Strip ... blocks + # Strip ... AND ... reasoning blocks. + # M2.7 (the active model) emits the lowercase tag; M3/Qwen emits . + # Both must be stripped before JSON extraction. import re s = re.sub(r".*?", "", s, flags=re.DOTALL) + s = re.sub(r".*?", "", s, flags=re.DOTALL) + s = re.sub(r".*?", "", s, flags=re.DOTALL) s = s.strip() # Strip markdown code fences if s.startswith("```"): @@ -475,14 +479,30 @@ def _normalize_extraction_payload(data: Dict[str, Any], model: Type[BaseModel]) # Only rename entity_type_id if missing and the model expects it if "entity_type_id" in model_field_set and "entity_type_id" not in item_norm: + # M2.7 commonly emits "entity_types": ["Country", "Entity"] (a plural + # list of strings) instead of the singular "type"/"entity_type". + # Try the singular aliases first, then the plural list. + chosen = None for alias in ("type", "entity_type", "label", "category", "kind"): if alias in item_norm: - val = item_norm.pop(alias) - if isinstance(val, int): - item_norm["entity_type_id"] = val - elif isinstance(val, str): - item_norm["entity_type_id"] = _entity_type_id_for_label(val) + chosen = item_norm.pop(alias) break + if chosen is None and "entity_types" in item_norm: + v = item_norm.pop("entity_types") + if isinstance(v, list) and v: + for candidate in v: + if isinstance(candidate, str) and candidate.lower() != "entity": + chosen = candidate + break + if chosen is None: + chosen = v[0] if isinstance(v[0], str) else None + elif isinstance(v, str): + chosen = v + if chosen is not None: + if isinstance(chosen, int): + item_norm["entity_type_id"] = chosen + elif isinstance(chosen, str): + item_norm["entity_type_id"] = _entity_type_id_for_label(chosen) # episode_indices default — M3 sometimes emits {"item": "0"} (JSON-schema # items shape) or a bare string instead of a list of ints. diff --git a/frontend/src/api/graph.js b/frontend/src/api/graph.js index ef90a2b6..70d81e91 100644 --- a/frontend/src/api/graph.js +++ b/frontend/src/api/graph.js @@ -68,3 +68,16 @@ export function getProject(projectId) { method: 'get' }) } + +/** + * 列出项目(最近 N 个),用于首页 history 视图显示 + * @param {Number} limit + * @returns {Promise} + */ +export function listProjects(limit = 20) { + return service({ + url: '/api/graph/project/list', + method: 'get', + params: { limit } + }) +} diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index e840e116..cf19910c 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -3,7 +3,7 @@ import i18n from '../i18n' // 创建axios实例 const service = axios.create({ - baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5001', + baseURL: import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5001', timeout: 300000, // 5分钟超时(本体生成可能需要较长时间) headers: { 'Content-Type': 'application/json' diff --git a/frontend/src/components/HistoryDatabase.vue b/frontend/src/components/HistoryDatabase.vue index d6c6e9a5..7df8aa56 100644 --- a/frontend/src/components/HistoryDatabase.vue +++ b/frontend/src/components/HistoryDatabase.vue @@ -21,7 +21,7 @@ { const loadHistory = async () => { try { loading.value = true - const response = await getSimulationHistory(20) - if (response.success) { - projects.value = response.data || [] + // Fetch BOTH simulations and projects in parallel, then merge by project_id + // so the homepage history shows the user's actual graph work, not just + // agent-run simulations (which most users will have zero of). + const [simResp, projResp] = await Promise.all([ + getSimulationHistory(20).catch(() => ({ success: false, data: [] })), + listProjects(20).catch(() => ({ success: false, data: [] })), + ]) + + const sims = (simResp?.success && Array.isArray(simResp.data)) ? simResp.data : [] + const projs = (projResp?.success && Array.isArray(projResp.data)) ? projResp.data.map(projectToHistoryItem) : [] + + // Merge with simulations taking priority (they have richer fields), + // but include every project the user has ever built a graph for. + const seen = new Set() + const merged = [] + for (const s of sims) { + if (s.project_id) seen.add(s.project_id) + merged.push(s) } + for (const p of projs) { + if (!seen.has(p.project_id)) merged.push(p) + } + // Newest first + merged.sort((a, b) => (b.created_at || '').localeCompare(a.created_at || '')) + + projects.value = merged } catch (error) { console.error('加载历史项目失败:', error) projects.value = [] diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 62d23201..35279332 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,5 +1,6 @@ import { createRouter, createWebHistory } from 'vue-router' import Home from '../views/Home.vue' +import GraphView from '../views/GraphView.vue' import Process from '../views/MainView.vue' import SimulationView from '../views/SimulationView.vue' import SimulationRunView from '../views/SimulationRunView.vue' @@ -12,6 +13,12 @@ const routes = [ name: 'Home', component: Home }, + { + path: '/graph/:id', + name: 'Graph', + component: GraphView, + props: true + }, { path: '/process/:projectId', name: 'Process', diff --git a/frontend/src/views/GraphView.vue b/frontend/src/views/GraphView.vue new file mode 100644 index 00000000..e7e5ce9c --- /dev/null +++ b/frontend/src/views/GraphView.vue @@ -0,0 +1,71 @@ + + + + + Loading graph… + graph_id: {{ graphId }} + {{ error }} + + + + + + +
graph_id: {{ graphId }}
{{ graphId }}
{{ error }}