chore: 合入 stash Ops hardening 与 migration 000041
Ask/catalog 权限与审计加固、量表读权限统一,以及未提交的 ops hardening 变更。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -272,8 +272,18 @@ export const adminApi = {
|
||||
message_count: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
messages: Array<{ id: string; role: string; content: string; created_at: string }>
|
||||
}>('GET', `/ask/threads/${id}`),
|
||||
askThreadMessages: (id: string) =>
|
||||
request<{
|
||||
id: string
|
||||
user_id: string
|
||||
profile_id: string
|
||||
scene?: string
|
||||
message_count: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
messages: Array<{ id: string; role: string; content: string; created_at: string }>
|
||||
}>('GET', `/ask/threads/${id}/messages`),
|
||||
filterRules: () =>
|
||||
request<{
|
||||
items: Array<{
|
||||
@@ -482,7 +492,7 @@ export const adminApi = {
|
||||
request<Record<string, unknown>>('GET', `/ai/tools/${id}`),
|
||||
blockPolicies: () =>
|
||||
request<{ items: Array<Record<string, unknown>> }>('GET', '/content-safety/block-policies'),
|
||||
blockPolicie: (id: string) =>
|
||||
blockPolicy: (id: string) =>
|
||||
request<Record<string, unknown>>('GET', `/content-safety/block-policies/${id}`),
|
||||
cases: () =>
|
||||
request<{ items: Array<Record<string, unknown>> }>('GET', '/content-safety/cases'),
|
||||
|
||||
@@ -5,7 +5,9 @@ import { adminApi } from '@/api/client'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
type Thread = Awaited<ReturnType<typeof adminApi.askThreads>>['items'][number]
|
||||
type Detail = Awaited<ReturnType<typeof adminApi.askThread>>
|
||||
type Detail = Awaited<ReturnType<typeof adminApi.askThread>> & {
|
||||
messages?: Array<{ id: string; role: string; content: string; created_at: string }>
|
||||
}
|
||||
type Feedback = Awaited<ReturnType<typeof adminApi.askFeedback>>['items'][number]
|
||||
|
||||
const auth = useAuthStore()
|
||||
@@ -20,8 +22,10 @@ const rating = ref(4)
|
||||
const tag = ref('helpful')
|
||||
const note = ref('')
|
||||
const fbMsg = ref('')
|
||||
const transcriptDenied = ref(false)
|
||||
|
||||
const canWriteFeedback = () => auth.can('admin.ask.feedback.write')
|
||||
const canReadTranscript = () => auth.can('admin.ask.transcript.read')
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
@@ -42,8 +46,16 @@ async function openThread(id: string) {
|
||||
detailErr.value = ''
|
||||
selected.value = null
|
||||
fbMsg.value = ''
|
||||
transcriptDenied.value = false
|
||||
try {
|
||||
selected.value = await adminApi.askThread(id)
|
||||
const meta = await adminApi.askThread(id)
|
||||
selected.value = { ...meta, messages: [] }
|
||||
if (canReadTranscript()) {
|
||||
const full = await adminApi.askThreadMessages(id)
|
||||
selected.value = full
|
||||
} else {
|
||||
transcriptDenied.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
detailErr.value = e instanceof Error ? e.message : '详情失败'
|
||||
} finally {
|
||||
@@ -123,8 +135,9 @@ onMounted(load)
|
||||
{{ selected.scene || '—' }} · {{ selected.message_count }} 条 ·
|
||||
<RouterLink :to="`/users/${selected.user_id}`">用户</RouterLink>
|
||||
</p>
|
||||
<ul class="msgs">
|
||||
<li v-for="m in selected.messages" :key="m.id" :class="m.role">
|
||||
<p v-if="transcriptDenied" class="muted">无对话原文权限(admin.ask.transcript.read)</p>
|
||||
<ul v-else class="msgs">
|
||||
<li v-for="m in selected.messages || []" :key="m.id" :class="m.role">
|
||||
<span class="role">{{ m.role }}</span>
|
||||
<p>{{ m.content }}</p>
|
||||
<time>{{ fmtTime(m.created_at) }}</time>
|
||||
|
||||
@@ -5,15 +5,15 @@ import { adminApi } from '@/api/client'
|
||||
type Row = { id: string; code?: string; slug?: string; title?: string; status?: string }
|
||||
|
||||
const catalogs = [
|
||||
{ key: 'publications', label: '定时发布', load: () => adminApi.publications() },
|
||||
{ key: 'knowledgeChunks', label: '知识块', load: () => adminApi.knowledgeChunks() },
|
||||
{ key: 'tools', label: '工具定义', load: () => adminApi.tools() },
|
||||
{ key: 'blockPolicies', label: '拦截策略', load: () => adminApi.blockPolicies() },
|
||||
{ key: 'cases', label: '审核案', load: () => adminApi.cases() },
|
||||
{ key: 'events', label: '危机事件', load: () => adminApi.events() },
|
||||
{ key: 'interventions', label: '干预结果', load: () => adminApi.interventions() },
|
||||
{ key: 'handoffs', label: '转接案', load: () => adminApi.handoffs() },
|
||||
{ key: 'privacy', label: '隐私请求', load: () => adminApi.requests() },
|
||||
{ key: 'publications', label: '定时发布(目录)', load: () => adminApi.publications() },
|
||||
{ key: 'knowledgeChunks', label: '知识块(目录)', load: () => adminApi.knowledgeChunks() },
|
||||
{ key: 'tools', label: '工具定义(目录)', load: () => adminApi.tools() },
|
||||
{ key: 'blockPolicies', label: '拦截策略(目录)', load: () => adminApi.blockPolicies() },
|
||||
{ key: 'cases', label: '审核类型(目录)', load: () => adminApi.cases() },
|
||||
{ key: 'events', label: '危机类型(目录)', load: () => adminApi.events() },
|
||||
{ key: 'interventions', label: '干预类型(目录)', load: () => adminApi.interventions() },
|
||||
{ key: 'handoffs', label: '转接类型(目录)', load: () => adminApi.handoffs() },
|
||||
{ key: 'privacy', label: '隐私类型(目录)', load: () => adminApi.requests() },
|
||||
{ key: 'star', label: '星座配置', load: () => adminApi.starConfigs() },
|
||||
{ key: 'rhythm', label: '节律配置', load: () => adminApi.rhythmConfigs() },
|
||||
{ key: 'decks', label: '意象牌组', load: () => adminApi.imageCardDecks() },
|
||||
@@ -50,7 +50,7 @@ onMounted(() => {
|
||||
<template>
|
||||
<section>
|
||||
<h1>目录只读仓</h1>
|
||||
<p class="muted">ECR-026…040 运营只读目录聚合(不可写发布)</p>
|
||||
<p class="muted">ECR-026…040 运营只读目录聚合(类型占位,非用户个案;见 docs/WAVE0/CATALOG_VS_CASE.md)</p>
|
||||
<div class="tabs">
|
||||
<button
|
||||
v-for="(c, i) in catalogs"
|
||||
|
||||
Reference in New Issue
Block a user