chore: 工作台产品化进行中的改动

把工作区里其余在制品一并入库,主要是工作台产品化的推进:

  后端:新增 capability_definition / project / my_app_center / office_skill
        接口与 action_definition / skill_definition / project / user_app_center
        模型,config 加路由健康上报。
  前端:新增 frontend/src/skills(Office 技能与 workbuddy 复刻)、
        项目管理、应用中心、能力目录页,以及配套 api / store / config;
        聊天侧新增 SpecialistChip / SpecialistPanel / SkillStrip / AppChatRail
        等组件。
  清理:移除旧 views/tools 下的单页工具(已并入工作台)、_frozen 冻结组件、
        cmd/inspect_oa_debug 调试入口,以及两份调试笔记。
  其它:文档与启动脚本同步。

(这批改动与上一提交的 SY23 工作并行进行,此前已在同一工作区内交织。)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
eaiadmin
2026-09-17 21:32:35 +08:00
co-authored by Claude Code
parent 8b136d4a10
commit 16d63de4e1
180 changed files with 22283 additions and 13850 deletions
@@ -1,214 +0,0 @@
package main
import (
"database/sql"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"strings"
"time"
_ "modernc.org/sqlite"
)
type taskRow struct {
ID int64 `json:"id"`
Title string `json:"title"`
Status string `json:"status"`
UpdatedAt string `json:"updated_at"`
}
type articleRow struct {
TaskID int64 `json:"task_id"`
Keyword string `json:"keyword"`
SelectedTopic string `json:"selected_topic"`
TopicHeat string `json:"topic_heat"`
OutlineStyle string `json:"outline_style"`
OutlineWords int `json:"outline_words"`
ContentTargetWords int `json:"content_target_words"`
HotspotsJSON string `json:"hotspots_json"`
TopicCandidatesJSON string `json:"topic_candidates_json"`
}
type runRow struct {
ID int64 `json:"id"`
TaskID int64 `json:"task_id"`
ActionKey string `json:"action_key"`
ActionTitle string `json:"action_title"`
OutputJSON string `json:"output_json"`
LogsJSON string `json:"logs_json"`
StartedAt string `json:"started_at"`
}
func main() {
taskID := flag.Int64("task", 0, "official account task id")
resetStuck := flag.Bool("reset-stuck", false, "reset stuck running state for the given task")
flag.Parse()
dbPath := `d:\traecode\pj0235-eai_agentplatform\eai_ap_app\backend-go\data\eai_agentplatform.db`
db, err := sql.Open("sqlite", dbPath)
if err != nil {
log.Fatal(err)
}
defer db.Close()
if *resetStuck {
if *taskID <= 0 {
log.Fatal("please pass -task when using -reset-stuck")
}
if err := resetOfficialAccountTaskState(db, *taskID); err != nil {
log.Fatal(err)
}
fmt.Printf("reset stuck running state for task %d\n", *taskID)
return
}
tasks := make([]taskRow, 0, 5)
taskRows, err := db.Query(`
select id, title, status, updated_at
from worker_task
where specialist_key = 'wechat-official-account'
order by id desc
limit 5
`)
if err != nil {
log.Fatal(err)
}
for taskRows.Next() {
var row taskRow
if err := taskRows.Scan(&row.ID, &row.Title, &row.Status, &row.UpdatedAt); err != nil {
log.Fatal(err)
}
tasks = append(tasks, row)
}
_ = taskRows.Close()
var article articleRow
_ = db.QueryRow(`
select task_id, keyword, selected_topic, topic_heat, outline_style, outline_words, content_target_words, hotspots_json, topic_candidates_json
from official_account_article
order by id desc
limit 1
`).Scan(
&article.TaskID,
&article.Keyword,
&article.SelectedTopic,
&article.TopicHeat,
&article.OutlineStyle,
&article.OutlineWords,
&article.ContentTargetWords,
&article.HotspotsJSON,
&article.TopicCandidatesJSON,
)
runs := make([]runRow, 0, 5)
runRows, err := db.Query(`
select id, task_id, action_key, action_title, output_json, logs_json, started_at
from worker_run
where task_id = ?
order by id desc
limit 5
`, article.TaskID)
if err != nil {
log.Fatal(err)
}
for runRows.Next() {
var row runRow
if err := runRows.Scan(&row.ID, &row.TaskID, &row.ActionKey, &row.ActionTitle, &row.OutputJSON, &row.LogsJSON, &row.StartedAt); err != nil {
log.Fatal(err)
}
runs = append(runs, row)
}
_ = runRows.Close()
var hotspotCount int
_ = db.QueryRow(`select count(1) from official_account_hotspot`).Scan(&hotspotCount)
result := map[string]any{
"tasks": tasks,
"latest_article": article,
"latest_runs": runs,
"hotspot_count": hotspotCount,
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(result); err != nil {
fmt.Println(err)
}
}
func resetOfficialAccountTaskState(db *sql.DB, taskID int64) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
_ = tx.Rollback()
}
}()
var contextJSON string
if err = tx.QueryRow(`select context_json from worker_task where id = ?`, taskID).Scan(&contextJSON); err != nil {
return err
}
type stepState struct {
Status string `json:"status"`
Progress int `json:"progress"`
ProgressText string `json:"progress_text"`
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
Logs []string `json:"logs"`
Output map[string]interface{} `json:"output"`
}
type workflowState struct {
CurrentStep string `json:"current_step"`
Steps map[string]*stepState `json:"steps"`
}
var workflow workflowState
if strings.TrimSpace(contextJSON) != "" {
if err = json.Unmarshal([]byte(contextJSON), &workflow); err != nil {
return err
}
}
if workflow.Steps == nil {
workflow.Steps = map[string]*stepState{}
}
for _, key := range []string{"topic_recommendation", "title_generation", "outline_generation", "content_creation"} {
step := workflow.Steps[key]
if step == nil || step.Status != "running" {
continue
}
step.Status = "error"
step.ProgressText = "上次执行已中断,请重新执行"
step.FinishedAt = time.Now().Format(time.RFC3339)
step.Logs = append(step.Logs, time.Now().Format("15:04:05")+" 执行已中断,已自动停止")
}
workflow.CurrentStep = "topic_recommendation"
newContext, err := json.Marshal(workflow)
if err != nil {
return err
}
if _, err = tx.Exec(`
update worker_task
set status = ?, current_result = ?, context_json = ?, updated_at = CURRENT_TIMESTAMP
where id = ?
`, "待处理", "", string(newContext), taskID); err != nil {
return err
}
if _, err = tx.Exec(`
update worker_run
set status = ?, output_json = ?, logs_json = ?, finished_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
where task_id = ? and status = ?
`, "error", `{"summary":"上次执行已中断,请重新执行","error":"interrupted"}`, `["执行已中断,已自动停止"]`, taskID, "running"); err != nil {
return err
}
return tx.Commit()
}
@@ -36,6 +36,8 @@ func main() {
log.Fatalf("种子数据初始化失败: %v", err)
}
config.StartAIRouteHealthLoop(30 * time.Minute)
// 定期备份:SQLite 是单文件且无外部副本,坏一份就是全丢。
// 失败只在日志里报,绝不因此拦下服务。
if cfg.BackupEnabled {