EngineWorkspaceView.vue 5.02 KB
<script setup lang="ts">
import { computed } from 'vue'

import EngineWorkbench from '@/components/engines/EngineWorkbench.vue'
import { useWorkbenchControllerContext } from '@/composables/useWorkbenchController'

const controller = useWorkbenchControllerContext()

const activeEngine = computed(() => (
  controller.engineOverview.value.find((engine) => engine.name === controller.system.activeApp.value)
  ?? controller.engineOverview.value[0]
))
</script>

<template>
  <section class="engine-page">
    <div class="engine-header archive-shell-block">
      <div class="engine-header__copy">
        <p class="archive-kicker">Integrated Analysis Room</p>
        <h2 class="archive-page-title">BettaFish Workbench</h2>
        <p class="archive-copy">
          把 Insight、Media、Query 与 Forum 收在同一个研判空间里。当前激活引擎的上下文、讨论流和控制台输出会始终对齐最新研究指令。
        </p>
      </div>

      <div class="engine-header__status">
        <article class="archive-stat">
          <p>Active Engine</p>
          <strong>{{ activeEngine?.label || 'Forum 研判' }}</strong>
          <span>{{ activeEngine?.status || 'stopped' }}</span>
        </article>
        <article class="archive-stat">
          <p>System State</p>
          <strong>{{ controller.system.systemStatus.value.started ? 'Stable' : 'Await Boot' }}</strong>
          <span>{{ controller.busy.value ? '当前有研究指令正在执行。' : '当前可继续分发新的研究问题。' }}</span>
        </article>
      </div>
    </div>

    <div class="engine-switches">
      <button
        v-for="engine in controller.engineOverview.value"
        :key="engine.name"
        class="engine-switch"
        :class="{ 'engine-switch--active': engine.name === controller.system.activeApp.value }"
        type="button"
        @click="controller.updateActiveApp(engine.name)"
      >
        <span class="engine-switch__dot" :data-tone="engine.tone" />
        <div>
          <p>{{ engine.label }}</p>
          <span>{{ engine.url || '内嵌工作区 / 讨论流' }}</span>
        </div>
        <small>{{ engine.status }}</small>
      </button>
    </div>

    <EngineWorkbench
      :system-status="controller.system.systemStatus.value"
      :app-status="controller.system.appStatus.value"
      :active-app="controller.system.activeApp.value"
      :active-engine-url="controller.system.activeEngineUrl.value"
      :console-lines="controller.system.consoleLines.value"
      :forum-messages="controller.system.forumMessages.value"
      :query="controller.research.searchQuery.value"
      :busy="controller.busy.value"
      @update:active-app="controller.updateActiveApp"
      @run-search="controller.runResearch"
      @refresh-console="controller.system.refreshConsole"
    />
  </section>
</template>

<style scoped>
.engine-page,
.engine-header,
.engine-header__status,
.engine-switches {
  display: grid;
  gap: 18px;
}

.engine-page {
  max-width: 1560px;
  margin: 0 auto;
}

.engine-header {
  grid-template-columns: minmax(0, 1fr) minmax(360px, 0.82fr);
  padding: 30px;
  border-radius: 30px;
  background:
    radial-gradient(circle at top left, rgba(22, 52, 45, 0.12), transparent 24%),
    linear-gradient(135deg, rgba(255, 251, 246, 0.94), rgba(243, 236, 226, 0.92));
}

.engine-header__copy {
  display: grid;
  gap: 14px;
}

.engine-header__status {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.engine-switches {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.engine-switch {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 14px;
  align-items: start;
  padding: 20px;
  border: 1px solid rgba(24, 35, 31, 0.08);
  border-radius: 22px;
  background: rgba(255, 252, 247, 0.78);
  box-shadow: var(--shadow-soft);
  text-align: left;
  cursor: pointer;
}

.engine-switch--active {
  border-color: rgba(141, 103, 56, 0.24);
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(248, 241, 232, 0.92));
}

.engine-switch__dot {
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: rgba(24, 35, 31, 0.18);
  margin-top: 6px;
}

.engine-switch__dot[data-tone='success'] {
  background: var(--success);
}

.engine-switch__dot[data-tone='running'] {
  background: var(--warning);
}

.engine-switch__dot[data-tone='danger'] {
  background: var(--danger);
}

.engine-switch p {
  margin: 0;
  font-size: 18px;
  color: var(--primary);
  font-family: var(--font-display);
}

.engine-switch span,
.engine-switch small {
  color: var(--text-muted);
}

.engine-switch span {
  display: block;
  margin-top: 6px;
  line-height: 1.7;
}

.engine-switch small {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

@media (max-width: 1360px) {
  .engine-header,
  .engine-switches {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 820px) {
  .engine-header,
  .engine-header__status,
  .engine-switches {
    grid-template-columns: 1fr;
  }

  .engine-header {
    padding: 22px;
  }

  .engine-switch {
    grid-template-columns: auto 1fr;
  }
}
</style>