Files
pj0235-eai_agentplatform/start_dev_10231_10232.sh
eaiadminandClaude Code c1af86c934 feat(asr): 本地语音转写接入为一级路由 + 并行工作流合并提交
按用户指示做**一包提交**,不按工作流拆分。本提交刻意混合了多条并行线:

  · 本地 ASR 接管:audio 成为与 chat/embed/image/video 同等的路由类别
    (IsLocalRoute 单一判据、audio 健康探测、default_audio_route、
    auto 占位、GET /api/ai/routes/audio、回退云端时界面明示「音频已出网」)
  · LLM 调用层:ctx 贯穿、ToolCall/ToolSchema、EmptyCompletionError /
    TransientUpstreamError(按错误类型而非文案判重试)
  · 编排 Agent:general_assistant orchestrate/persistence/spec_driver
  · 联网搜索:internal/search(playwright)
  · 网盘:backend + 前端
  · 前端 UI:导航/路由/工作台若干页
  · 交付文档:DELIVERY.md / AR04 / 部署文档的「无 Python」表述据实改写,
    新增 eai_agentplatform-asr.service、asr.env、clonezilla-cleanup 清 ~/asr-poc

不分拆的原因:dev 早期,粒度不该打断工作节奏。且实测过——这些改动
**在编译上是同一个单元**(llm.go 的 ctx 签名变更牵动 12 个调用点,
chat_message.go 的 ctx 改动又与编排重写同处一个 hunk),拆出来的中间态编不过。
详见 TOP_CODING_RULES.md G14.5 与 bugs_and_errors.md E09。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-26 22:21:39 +08:00

409 lines
12 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
if [ -z "${BASH_VERSION:-}" ]; then
if command -v bash >/dev/null 2>&1; then
echo "[INFO] start_dev_10231_10232.sh requires bash. Re-running with bash ..."
exec bash "$0" "$@"
fi
echo "[FATAL] bash is required but was not found on PATH." >&2
echo " Please run: bash ./start_dev_10231_10232.sh" >&2
exit 1
fi
set -euo pipefail
FORCE=0
SKIP_PREFLIGHT=0
SKIP_SMOKE=0
SKIP_BUILD=0
while [[ $# -gt 0 ]]; do
case "$1" in
--force|-f)
FORCE=1
;;
--skip-preflight)
SKIP_PREFLIGHT=1
;;
--skip-smoke)
SKIP_SMOKE=1
;;
--skip-build)
SKIP_BUILD=1
;;
--help|-h)
cat <<'EOF'
Usage: ./start_dev_10231_10232.sh [options]
Options:
--force Kill whatever holds the ports, even non-project processes
(a previous run of this script is always stopped and
replaced automatically)
--skip-preflight Skip preflight checks
--skip-smoke Skip admin account smoke login
--skip-build Do not rebuild the backend, even if sources are newer
--help Show this help
EOF
exit 0
;;
*)
echo "[FATAL] Unknown option: $1" >&2
exit 1
;;
esac
shift
done
# ================================================================
# 端口说明
# 10231 - 前端 Vite 开发服务器 (dev only)
# 10232 - 后端 Go API 服务 (dev only)
# 8080 - 外部 LLM 服务 (llamacpp) — AI 对话 API 入口
# 8081 - 外部 Embed 服务 (llamacpp) — 向量嵌入服务
# 11434 - 外部 Ollama 服务 (如使用 Ollama 路由)
# ================================================================
BACKEND_PORT=10232
FRONTEND_PORT=10231
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_DIR="$REPO_DIR/eai_agentplatform"
BACKEND_DIR="$APP_DIR/backend-go"
BACKEND_BIN="$BACKEND_DIR/bin/eai_agentplatform-server"
FRONTEND_DIR="$APP_DIR/frontend"
DEV_BIND_HOST="${EAI_DEV_BIND_HOST:-0.0.0.0}"
DEBUG_LOG_DIR="$REPO_DIR/debuglog"
mkdir -p "$DEBUG_LOG_DIR"
RUN_ID="$(date +%Y%m%d_%H%M%S)"
BACKEND_LOG="$DEBUG_LOG_DIR/backend_${RUN_ID}.log"
FRONTEND_LOG="$DEBUG_LOG_DIR/frontend_${RUN_ID}.log"
BACKEND_PID_FILE="$DEBUG_LOG_DIR/backend.pid"
FRONTEND_PID_FILE="$DEBUG_LOG_DIR/frontend.pid"
# 加载本地运行 secrets(.env 已被 .gitignore 忽略,不进版本库)。
# 例如 SERPAPI_API_KEY 等环境变量,供后端进程继承(backed 用 `env PORT=... $BIN` 启动,
# 会继承本 shell 导出的所有环境变量)。文件不存在时静默跳过。
if [ -f "$APP_DIR/.env" ]; then
set -a
# shellcheck disable=SC1091
source "$APP_DIR/.env"
set +a
fi
check() {
local label="$1"
local fix_hint="$2"
shift 2
printf ' [.] %s ...' "$label"
if "$@" >/dev/null 2>&1; then
printf ' OK\n'
return 0
fi
printf ' FAIL\n'
if [[ -n "$fix_hint" ]]; then
printf ' fix: %s\n' "$fix_hint"
fi
return 1
}
wait_health() {
local url="$1"
local label="$2"
local timeout_sec="${3:-60}"
local elapsed=0
printf ' [.] waiting on %s (%s) ...' "$label" "$url"
while (( elapsed < timeout_sec )); do
if curl -fsS --max-time 2 "$url" >/dev/null 2>&1; then
printf ' READY\n'
return 0
fi
sleep 1
elapsed=$((elapsed + 1))
printf '.'
done
printf ' TIMEOUT\n'
return 1
}
detect_public_host() {
local candidate
for candidate in $(hostname -I 2>/dev/null || true); do
case "$candidate" in
127.*|172.17.*)
continue
;;
*)
echo "$candidate"
return 0
;;
esac
done
echo "127.0.0.1"
}
port_pids() {
local port="$1"
if command -v lsof >/dev/null 2>&1; then
lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | sort -u
return 0
fi
if command -v ss >/dev/null 2>&1; then
ss -ltnp "( sport = :$port )" 2>/dev/null | awk -F'pid=' 'NR>1 && NF>1 {split($2,a,","); print a[1]}' | sort -u
return 0
fi
return 1
}
# Decide whether a process belongs to this project.
#
# This must look at the full command line, not `ps -o comm=`: comm is capped at
# 15 characters (the kernel's TASK_COMM_LEN), so the backend reports itself as
# "eai_agentplatfo" and could never match its own name. That made the launcher
# treat its own backend as a foreign process and refuse to restart.
process_is_ours() {
local pid="$1"
local port="$2"
local args
args="$(ps -o args= -p "$pid" 2>/dev/null || true)"
[[ -z "$args" ]] && return 1
[[ "$args" == *"$BACKEND_BIN"* ]] && return 0
[[ "$args" == *vite* && "$args" == *"--port $port"* ]] && return 0
return 1
}
process_label() {
local pid="$1"
local args
args="$(ps -o args= -p "$pid" 2>/dev/null || true)"
if [[ -z "$args" ]]; then
printf 'unknown'
return 0
fi
args="${args%% *}"
printf '%s' "${args##*/}"
}
# Stop the run recorded by a previous invocation. Uses the pid files so a
# previous instance still gets cleaned up even if it is not holding its port.
kill_previous_run() {
local pid_file="$1"
local port="$2"
local label="$3"
local pid
[[ -f "$pid_file" ]] || return 0
pid="$(cat "$pid_file" 2>/dev/null || true)"
[[ "$pid" =~ ^[0-9]+$ ]] || return 0
kill -0 "$pid" 2>/dev/null || return 0
process_is_ours "$pid" "$port" || return 0
printf ' [.] stopping previous %s (PID %s)\n' "$label" "$pid"
kill "$pid" 2>/dev/null || true
sleep 1
kill -9 "$pid" 2>/dev/null || true
return 0
}
resolve_port() {
local port="$1"
local label="$2"
local pids
mapfile -t pids < <(port_pids "$port" || true)
if [[ ${#pids[@]} -eq 0 ]]; then
printf ' [.] %s :%s free\n' "$label" "$port"
return 0
fi
printf ' [!] %s :%s in use by ' "$label" "$port"
for pid in "${pids[@]}"; do
printf '%s(PID %s) ' "$(process_label "$pid")" "$pid"
done
printf '\n'
for pid in "${pids[@]}"; do
if [[ "$FORCE" -eq 1 ]] || process_is_ours "$pid" "$port"; then
printf ' killing previous %s(PID %s)\n' "$(process_label "$pid")" "$pid"
kill "$pid" 2>/dev/null || true
sleep 1
kill -9 "$pid" 2>/dev/null || true
else
printf ' keeping non-project process: %s(PID %s)\n' "$(process_label "$pid")" "$pid"
printf ' fix: stop that process manually, or rerun with --force\n'
return 1
fi
done
mapfile -t pids < <(port_pids "$port" || true)
if [[ ${#pids[@]} -gt 0 ]]; then
printf ' [FAIL] %s :%s still in use\n' "$label" "$port"
return 1
fi
printf ' [OK] %s :%s released\n' "$label" "$port"
return 0
}
# True when the backend binary is missing or older than its sources.
#
# Preflight only proves the binary exists, which happily launches stale code
# after a source edit — the failure is silent, so it needs an explicit check.
backend_needs_build() {
[[ -f "$BACKEND_BIN" ]] || return 0
local newer
newer="$(find "$BACKEND_DIR" -name '*.go' -newer "$BACKEND_BIN" -print -quit 2>/dev/null || true)"
[[ -n "$newer" ]] && return 0
local f
for f in "$BACKEND_DIR/go.mod" "$BACKEND_DIR/go.sum"; do
[[ -f "$f" && "$f" -nt "$BACKEND_BIN" ]] && return 0
done
return 1
}
build_backend() {
local build_log="$DEBUG_LOG_DIR/backend_build.log"
if ! command -v go >/dev/null 2>&1; then
printf ' [FAIL] backend sources changed, but go is not on PATH\n'
printf ' fix: install go, or rebuild manually then rerun with --skip-build\n'
return 1
fi
printf ' [.] backend sources newer than binary, rebuilding ...'
if ( cd "$BACKEND_DIR" && go build -o bin/eai_agentplatform-server ./cmd/server ) >"$build_log" 2>&1; then
printf ' OK\n'
return 0
fi
printf ' FAIL\n'
printf ' log : %s\n' "$build_log"
tail -n 20 "$build_log" 2>/dev/null | sed 's/^/ /' || true
return 1
}
cleanup_started() {
if [[ -f "$BACKEND_PID_FILE" ]]; then
kill "$(cat "$BACKEND_PID_FILE")" 2>/dev/null || true
fi
if [[ -f "$FRONTEND_PID_FILE" ]]; then
kill "$(cat "$FRONTEND_PID_FILE")" 2>/dev/null || true
fi
}
echo "==> eai_agentplatform dev launcher"
echo " repo : $REPO_DIR"
echo " backend : $BACKEND_DIR (port $BACKEND_PORT)"
echo " frontend : $FRONTEND_DIR (port $FRONTEND_PORT)"
echo " bind host: $DEV_BIND_HOST"
echo
if [[ "${EAI_DEV_PUBLIC_HOST:-}" != "" ]]; then
DEV_PUBLIC_HOST="$EAI_DEV_PUBLIC_HOST"
elif [[ "$DEV_BIND_HOST" == "0.0.0.0" ]]; then
DEV_PUBLIC_HOST="$(detect_public_host)"
else
DEV_PUBLIC_HOST="$DEV_BIND_HOST"
fi
if [[ "$SKIP_PREFLIGHT" -eq 0 ]]; then
echo "[1/4] preflight checks"
all_ok=1
check "backend dir exists" "run from repo root" test -d "$BACKEND_DIR" || all_ok=0
check "frontend dir exists" "run from repo root" test -d "$FRONTEND_DIR" || all_ok=0
check "frontend node_modules" "run npm install in frontend/" test -e "$FRONTEND_DIR/node_modules" || all_ok=0
check "node on PATH" "install nodejs" command -v node || all_ok=0
check "npm on PATH" "install npm" command -v npm || all_ok=0
check "curl on PATH" "install curl" command -v curl || all_ok=0
if [[ "$all_ok" -eq 0 ]]; then
echo
echo "[ABORT] preflight failed."
exit 1
fi
echo
else
echo "[1/4] preflight skipped (--skip-preflight)"
echo
fi
# Runs whether or not preflight did: a stale binary is a silent failure, and
# building before the old services are stopped means a compile error leaves the
# currently running stack untouched.
if [[ "$SKIP_BUILD" -eq 1 ]]; then
echo "[1/4] backend build check skipped (--skip-build)"
echo
elif backend_needs_build; then
if ! build_backend; then
echo
echo "[ABORT] backend build failed; services left as they were."
exit 1
fi
echo
else
echo "[1/4] backend binary up to date"
echo
fi
echo "[2/4] port conflict check"
kill_previous_run "$BACKEND_PID_FILE" "$BACKEND_PORT" "backend"
kill_previous_run "$FRONTEND_PID_FILE" "$FRONTEND_PORT" "frontend"
resolve_port "$BACKEND_PORT" "backend" || exit 1
resolve_port "$FRONTEND_PORT" "frontend" || exit 1
echo
trap cleanup_started EXIT
echo "[3/4] launch services"
echo " [.] starting backend (eai_agentplatform-server :$BACKEND_PORT)"
echo " [.] log : $BACKEND_LOG"
nohup env PORT="$BACKEND_PORT" "$BACKEND_BIN" >>"$BACKEND_LOG" 2>&1 </dev/null &
BACKEND_PID=$!
echo "$BACKEND_PID" > "$BACKEND_PID_FILE"
if ! wait_health "http://127.0.0.1:$BACKEND_PORT/api/health" "backend"; then
echo
echo "[ABORT] backend did not become healthy, frontend not started."
echo " check: $BACKEND_LOG"
exit 1
fi
echo " [.] starting frontend (vite :$FRONTEND_PORT)"
echo " [.] log : $FRONTEND_LOG"
nohup bash -lc "cd \"$FRONTEND_DIR\" && exec node ./node_modules/vite/bin/vite.js --host \"$DEV_BIND_HOST\" --port \"$FRONTEND_PORT\"" >>"$FRONTEND_LOG" 2>&1 </dev/null &
FRONTEND_PID=$!
echo "$FRONTEND_PID" > "$FRONTEND_PID_FILE"
echo
echo "[4/4] health check + smoke login"
f_ok=1
if ! wait_health "http://127.0.0.1:$FRONTEND_PORT/" "frontend"; then
f_ok=0
fi
if [[ "$SKIP_SMOKE" -eq 0 && "$f_ok" -eq 1 ]]; then
echo
echo " [.] smoke login admin account ..."
payload='{"username":"admin","password":"admin123"}'
status_code="$(curl -sS -o /dev/null -w '%{http_code}' \
-H 'Content-Type: application/json' \
-d "$payload" \
"http://127.0.0.1:$BACKEND_PORT/api/auth/login" || true)"
if [[ "$status_code" == "200" ]]; then
printf ' [OK] admin login success\n'
else
printf ' [FAIL] admin login (HTTP %s)\n' "$status_code"
fi
else
echo " [.] smoke login skipped"
fi
echo
echo "================================================================="
echo " Service status"
echo "================================================================="
echo " backend : http://$DEV_PUBLIC_HOST:$BACKEND_PORT"
echo " frontend : http://$DEV_PUBLIC_HOST:$FRONTEND_PORT"
echo " backend log : $BACKEND_LOG"
echo " frontend log: $FRONTEND_LOG"
echo " PIDs: backend=$BACKEND_PID frontend=$FRONTEND_PID"
echo "================================================================="
echo
trap - EXIT
echo "Usage: ./start_dev_10231_10232.sh [--force] [--skip-preflight] [--skip-build]"
echo " env: EAI_DEV_BIND_HOST=0.0.0.0 (listen all)"
echo " env: EAI_DEV_PUBLIC_HOST=x.x.x.x (public url)"
echo