v1.1.3: default server 0.0.0.0:80 + row-level clustering + filter UI + dataset selector
This commit is contained in:
+2
-19
@@ -899,7 +899,7 @@ def manual_run_analysis(request):
|
||||
algorithm = body.get('algorithm', 'hdbscan')
|
||||
min_cluster_size = int(body.get('min_cluster_size', 5))
|
||||
head = body.get('head')
|
||||
cluster_mode = body.get('cluster_mode', 'entity')
|
||||
cluster_mode = body.get('cluster_mode', 'raw')
|
||||
|
||||
def _analysis_fn(run, ctx):
|
||||
from analysis.session_store import SessionStore
|
||||
@@ -909,7 +909,7 @@ def manual_run_analysis(request):
|
||||
min_cluster_size = ctx['min_cluster_size']
|
||||
head = ctx.get('head')
|
||||
pk = ctx['pk']
|
||||
cluster_mode = ctx.get('cluster_mode', 'entity')
|
||||
cluster_mode = ctx.get('cluster_mode', 'raw')
|
||||
|
||||
store = SessionStore()
|
||||
try:
|
||||
@@ -931,23 +931,6 @@ def manual_run_analysis(request):
|
||||
|
||||
ds_id = upload_ds_id if store.get_dataset(upload_ds_id) else f'upload_{pk}'
|
||||
|
||||
# ── Cluster mode: entity aggregation vs raw ──
|
||||
if cluster_mode == 'entity':
|
||||
# Build entity profiles first, then cluster on entity dataset
|
||||
from analysis.tool_registry import _handle_build_entity_profiles
|
||||
import asyncio
|
||||
entity_result = asyncio.new_event_loop().run_until_complete(
|
||||
_handle_build_entity_profiles(dataset_id=ds_id, auto_detect=True)
|
||||
)
|
||||
if 'error' in entity_result:
|
||||
run.error_message = f'实体聚合失败: {entity_result["error"]}'
|
||||
run.status = 'failed'
|
||||
run.save(update_fields=['status', 'error_message'])
|
||||
return
|
||||
ds_id = entity_result['dataset_id']
|
||||
run.entity_count = entity_result.get('n_entities')
|
||||
run.save(update_fields=['entity_count'])
|
||||
|
||||
# Use the unified clustering pipeline (clustering → extraction → PCA)
|
||||
from analysis.views import _run_clustering_pipeline
|
||||
_run_clustering_pipeline(
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
server:
|
||||
host: 127.0.0.1
|
||||
port: 8000
|
||||
host: 0.0.0.0
|
||||
port: 80
|
||||
debug: false
|
||||
data:
|
||||
schema_strict: false
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ class Config(BaseModel):
|
||||
"""Typed configuration for TianXuan."""
|
||||
|
||||
class Server(BaseModel):
|
||||
host: str = '127.0.0.1'
|
||||
port: int = 8000
|
||||
host: str = '0.0.0.0'
|
||||
port: int = 80
|
||||
debug: bool = False
|
||||
|
||||
class Data(BaseModel):
|
||||
|
||||
@@ -111,19 +111,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cluster Mode -->
|
||||
<div class="card" id="clusterModeCard">
|
||||
<label style="font-weight:600;">聚类模式</label>
|
||||
<div style="display:flex;gap:1.5rem;margin-top:0.5rem;">
|
||||
<label><input type="radio" name="clusterMode" value="entity" checked onchange="clusterMode='entity'"> 实体聚合后聚类(默认)</label>
|
||||
<label><input type="radio" name="clusterMode" value="raw" onchange="clusterMode='raw'"> 逐行直接聚类</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter builder card -->
|
||||
<div class="card" id="filterCard" style="display:none;">
|
||||
<div class="card" id="filterCard" style="background:#f0f4ff;border-color:#c8d6e5;">
|
||||
<h3>🔍 数据筛选</h3>
|
||||
<div id="filterRows">
|
||||
<div id="filterNoDataset" style="text-align:center;color:#999;padding:1rem;font-size:0.9rem;">选择数据集后可用</div>
|
||||
<div id="filterRows" style="display:none;">
|
||||
<div class="filter-row" data-idx="0" style="display:flex;gap:0.5rem;align-items:center;margin-bottom:0.4rem;">
|
||||
<select class="filter-col" style="flex:1;padding:0.35rem;border:1px solid #ccc;border-radius:4px;"><option value="">-- 列 --</option></select>
|
||||
<select class="filter-op" style="width:130px;padding:0.35rem;border:1px solid #ccc;border-radius:4px;">
|
||||
@@ -139,12 +131,13 @@
|
||||
<button class="btn" onclick="removeFilterRow(this)" style="background:#dc3545;color:#fff;font-size:0.8rem;padding:0.2rem 0.5rem;display:none;">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin:0.5rem 0;display:flex;gap:0.5rem;align-items:center;">
|
||||
<div id="filterControls" style="display:none;margin:0.5rem 0;gap:0.5rem;align-items:center;">
|
||||
<label style="font-size:0.85rem;white-space:nowrap;">逻辑:</label>
|
||||
<select id="filterLogic" style="padding:0.35rem;border:1px solid #ccc;border-radius:4px;">
|
||||
<option value="and">AND</option><option value="or">OR</option>
|
||||
</select>
|
||||
<button class="btn" onclick="addFilterRow()" style="font-size:0.8rem;background:#e8f0fe;color:#4361ee;">+ 添加条件</button>
|
||||
<button class="btn" onclick="resetFilter()" style="font-size:0.8rem;background:#fce4ec;color:#c62828;">重置</button>
|
||||
<button class="btn btn-primary" onclick="applyFilter()" style="margin-left:auto;">应用筛选</button>
|
||||
</div>
|
||||
<div id="filterResult" style="display:none;margin-top:0.5rem;padding:0.5rem;background:#e8f5e9;border-radius:4px;font-size:0.85rem;"></div>
|
||||
@@ -181,7 +174,6 @@ const CATEGORIES = {
|
||||
let stepCounter = 0;
|
||||
const workflow = [];
|
||||
let running = false;
|
||||
var clusterMode = 'entity';
|
||||
|
||||
// ── Dataset selector with status badges ──
|
||||
const DATASETS_WITH_STATUS = {{ datasets_with_status_json|safe }};
|
||||
@@ -362,16 +354,16 @@ async function executeStep(idx) {
|
||||
resultDiv.style.display = 'none';
|
||||
const params = collectParams(idx);
|
||||
|
||||
// Cluster mode: skip build_entity_profiles in raw mode
|
||||
if (clusterMode === 'raw' && step.tool.name === 'build_entity_profiles') {
|
||||
// Raw row-level clustering: skip entity aggregation entirely
|
||||
if (step.tool.name === 'build_entity_profiles') {
|
||||
step.status = 'done';
|
||||
resultDiv.textContent = '[已跳过] 逐行直接聚类模式,无需实体聚合';
|
||||
resultDiv.style.display = 'block';
|
||||
renderSteps();
|
||||
return;
|
||||
}
|
||||
// Pass cluster_mode to run_clustering tool
|
||||
if (clusterMode === 'raw' && step.tool.name === 'run_clustering') {
|
||||
// Always cluster raw rows directly
|
||||
if (step.tool.name === 'run_clustering') {
|
||||
params.cluster_mode = 'raw';
|
||||
}
|
||||
|
||||
@@ -578,9 +570,30 @@ const DATASET_COLUMNS = {
|
||||
// ── Filter builder ──
|
||||
function onDatasetChange() {
|
||||
const dsId = document.getElementById('datasetSelect').value;
|
||||
const filterCard = document.getElementById('filterCard');
|
||||
if (!dsId) { filterCard.style.display = 'none'; return; }
|
||||
filterCard.style.display = 'block';
|
||||
const noDatasetMsg = document.getElementById('filterNoDataset');
|
||||
const filterRows = document.getElementById('filterRows');
|
||||
const filterControls = document.getElementById('filterControls');
|
||||
const filterResult = document.getElementById('filterResult');
|
||||
|
||||
if (!dsId) {
|
||||
// No dataset selected: show placeholder, hide filter UI
|
||||
noDatasetMsg.style.display = 'block';
|
||||
filterRows.style.display = 'none';
|
||||
filterControls.style.display = 'none';
|
||||
filterResult.style.display = 'none';
|
||||
// Clear column dropdowns
|
||||
document.querySelectorAll('.filter-col').forEach(sel => {
|
||||
sel.innerHTML = '<option value="">-- 请先选择数据集 --</option>';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Dataset selected: show filter UI, hide placeholder
|
||||
noDatasetMsg.style.display = 'none';
|
||||
filterRows.style.display = 'block';
|
||||
filterControls.style.display = 'flex';
|
||||
|
||||
// Populate column dropdowns
|
||||
const cols = DATASET_COLUMNS[dsId] || [];
|
||||
document.querySelectorAll('.filter-col').forEach(sel => {
|
||||
const cur = sel.value;
|
||||
@@ -591,7 +604,7 @@ function onDatasetChange() {
|
||||
|
||||
function addFilterRow() {
|
||||
const rows = document.querySelectorAll('.filter-row');
|
||||
if (rows.length >= 2) return; // MVP: max 2 rows
|
||||
if (rows.length >= 5) return; // max 5 rows
|
||||
const template = rows[0].cloneNode(true);
|
||||
template.querySelector('.filter-val').value = '';
|
||||
template.querySelector('.filter-col').selectedIndex = 0;
|
||||
@@ -608,6 +621,20 @@ function removeFilterRow(btn) {
|
||||
btn.closest('.filter-row').remove();
|
||||
}
|
||||
|
||||
function resetFilter() {
|
||||
// Remove all rows except the first, then reset the first row
|
||||
const rows = document.querySelectorAll('.filter-row');
|
||||
for (let i = rows.length - 1; i >= 1; i--) {
|
||||
rows[i].remove();
|
||||
}
|
||||
const firstRow = rows[0];
|
||||
firstRow.querySelector('.filter-val').value = '';
|
||||
firstRow.querySelector('.filter-col').selectedIndex = 0;
|
||||
firstRow.querySelector('.filter-op').selectedIndex = 0;
|
||||
firstRow.querySelector('button').style.display = 'none';
|
||||
document.getElementById('filterResult').style.display = 'none';
|
||||
}
|
||||
|
||||
function collectFilters() {
|
||||
const filters = [];
|
||||
document.querySelectorAll('.filter-row').forEach(row => {
|
||||
@@ -650,7 +677,7 @@ async function applyFilter() {
|
||||
|
||||
const resultDiv = document.getElementById('filterResult');
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.textContent = '筛选完成: ' + data.dataset_id + ' (' + data.row_count + ' 行)';
|
||||
resultDiv.innerHTML = '<strong>✅ 筛选完成</strong>: 数据集 <code>' + data.dataset_id + '</code>,共 <strong>' + data.row_count + '</strong> 行';
|
||||
showSuccess('筛选完成: ' + data.row_count + ' 行');
|
||||
} catch(e) {
|
||||
showError('筛选请求失败: ' + e.message);
|
||||
|
||||
Reference in New Issue
Block a user