feat(mobile): show new ask sources and direct citation navigation
- citation chips: 正式经验 (CASE_EXP) / 同事经验·有奖征答 (COMMUNITY) - citation navigation: CASE_EXP opens case detail via ?caseId=, COMMUNITY goes straight to question detail via community-question detailRef, falling back to the question list - disable human-help action when the answer was already marked helpful (server would 409)
This commit is contained in:
@@ -140,7 +140,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { onShow, onUnload } from '@dcloudio/uni-app';
|
||||
import { onLoad, onShow, onUnload } from '@dcloudio/uni-app';
|
||||
import type { CaseCurateResponse, CaseDetailResponse, CaseOrganizeResponse, CaseRecord, CaseSelectedFile, CaseUploadResponse } from '@/types/api';
|
||||
import { chooseCaseAudio, curateCase, getCaseCapabilities, getCaseDetail, getCaseMediaUrl, getCaseRecords, organizeCase, uploadCaseAudio } from '@/services/case';
|
||||
import { releaseAuthenticatedFileUrl } from '@/services/api';
|
||||
@@ -164,6 +164,8 @@ const status = ref<'idle' | 'uploading' | 'organizing' | 'curating'>('idle');
|
||||
const recordsLoading = ref(false);
|
||||
const caseDetailLoading = ref(false);
|
||||
const message = ref('');
|
||||
/** 引用跳转带入的 caseId:加载完列表后自动打开对应详情,消费一次即清空。 */
|
||||
const pendingCaseId = ref('');
|
||||
const busy = computed(() => status.value !== 'idle');
|
||||
const statusText = computed(() => {
|
||||
if (status.value === 'uploading') return '转写中';
|
||||
@@ -229,6 +231,11 @@ const loadRecords = async () => {
|
||||
recordsLoading.value = true;
|
||||
try {
|
||||
records.value = await getCaseRecords();
|
||||
if (pendingCaseId.value) {
|
||||
const caseId = pendingCaseId.value;
|
||||
pendingCaseId.value = '';
|
||||
void openCaseDetail({ caseId });
|
||||
}
|
||||
} catch (error) {
|
||||
handleRequestError(error, '最近案例加载失败', true);
|
||||
} finally {
|
||||
@@ -258,7 +265,7 @@ const refreshRecords = () => {
|
||||
void loadRecords();
|
||||
};
|
||||
|
||||
const openCaseDetail = async (record: CaseRecord) => {
|
||||
const openCaseDetail = async (record: Pick<CaseRecord, 'caseId'>) => {
|
||||
if (!record.caseId || caseDetailLoading.value) return;
|
||||
caseDetailLoading.value = true;
|
||||
caseMediaLoading.value = true;
|
||||
@@ -332,6 +339,10 @@ const handleShow = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
onLoad((options) => {
|
||||
pendingCaseId.value = typeof options?.caseId === 'string' && options.caseId ? options.caseId : '';
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
void handleShow();
|
||||
});
|
||||
|
||||
@@ -263,12 +263,12 @@
|
||||
|
||||
<view v-if="msg.citationsOpen && msg.result.snippets?.length" class="bubble-citations">
|
||||
<component
|
||||
:is="snippet.detailRef && snippet.sourceType === 'DOCUMENT' ? 'button' : 'view'"
|
||||
:is="citationClickable(snippet) ? 'button' : 'view'"
|
||||
v-for="(snippet, index) in msg.result.snippets"
|
||||
:key="`${snippet.docTitle}-${index}`"
|
||||
class="citation"
|
||||
:class="{ 'citation-action': Boolean(snippet.detailRef && snippet.sourceType === 'DOCUMENT') }"
|
||||
@click="openCitationDetail(snippet)"
|
||||
:class="{ 'citation-action': citationClickable(snippet) }"
|
||||
@click="openCitation(snippet)"
|
||||
>
|
||||
<text class="citation-domain" :class="{ project: snippet.domain === 'PROJECT_SERVICE' }">
|
||||
{{ citationDomainLabel(snippet) }}
|
||||
@@ -283,6 +283,9 @@
|
||||
<text class="citation-location">{{ citationLocationLabel(snippet) }}</text>
|
||||
<text class="citation-open">查看出处 ›</text>
|
||||
</view>
|
||||
<view v-else-if="citationClickable(snippet)" class="citation-open-row">
|
||||
<text class="citation-open">查看出处 ›</text>
|
||||
</view>
|
||||
</component>
|
||||
</view>
|
||||
|
||||
@@ -1355,6 +1358,8 @@ const citationDomainLabel = (snippet: KnowledgeSearchResponse['snippets'][number
|
||||
if (snippet.domain === 'PROJECT_SERVICE') return '项目记录';
|
||||
if (snippet.domain === 'PERSONAL') return '我的资料';
|
||||
if (snippet.sourceType === 'DATA_TOOL') return '数据工具';
|
||||
if (snippet.sourceType === 'CASE_EXP') return '正式经验';
|
||||
if (snippet.sourceType === 'COMMUNITY') return '同事经验·有奖征答';
|
||||
return ({
|
||||
FORMAL_POLICY: '正式制度',
|
||||
OPERATING_MANUAL: '操作手册',
|
||||
@@ -1395,6 +1400,47 @@ const openCitationDetail = (snippet: KnowledgeSearchResponse['snippets'][number]
|
||||
uni.navigateTo({ url: `/pages/user/sop/citation-detail?ref=${encodeURIComponent(snippet.detailRef)}` });
|
||||
};
|
||||
|
||||
/** 从引用字段解析新来源业务 ID:docId 的 case:{caseId} / community-answer:{answerId},或 detailRef 的 community-question:{questionId};解析不到即空串。 */
|
||||
const citationSourceId = (
|
||||
snippet: KnowledgeSearchResponse['snippets'][number],
|
||||
prefix: string,
|
||||
field: 'docId' | 'detailRef' = 'docId'
|
||||
) => {
|
||||
const value = (field === 'detailRef' ? snippet.detailRef : snippet.docId) || '';
|
||||
return value.startsWith(prefix) ? value.slice(prefix.length) : '';
|
||||
};
|
||||
|
||||
const citationClickable = (snippet: KnowledgeSearchResponse['snippets'][number]) => {
|
||||
if (snippet.detailRef && snippet.sourceType === 'DOCUMENT') return true;
|
||||
if (snippet.sourceType === 'CASE_EXP') return Boolean(citationSourceId(snippet, 'case:'));
|
||||
if (snippet.sourceType === 'COMMUNITY') return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
/** 出处跳转:DOCUMENT 走 citation-detail;CASE_EXP 去案例页按 caseId 自动打开详情;COMMUNITY 按 detailRef 直达问题详情,无则回退列表。 */
|
||||
const openCitation = (snippet: KnowledgeSearchResponse['snippets'][number]) => {
|
||||
if (!citationClickable(snippet)) return;
|
||||
if (snippet.detailRef && snippet.sourceType === 'DOCUMENT') {
|
||||
openCitationDetail(snippet);
|
||||
return;
|
||||
}
|
||||
if (snippet.sourceType === 'CASE_EXP') {
|
||||
const caseId = citationSourceId(snippet, 'case:');
|
||||
if (caseId) {
|
||||
uni.navigateTo({ url: `/pages/user/cases/index?caseId=${encodeURIComponent(caseId)}` });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (snippet.sourceType === 'COMMUNITY') {
|
||||
const questionId = citationSourceId(snippet, 'community-question:', 'detailRef');
|
||||
if (questionId) {
|
||||
uni.navigateTo({ url: `/pages/user/questions/detail?id=${encodeURIComponent(questionId)}` });
|
||||
return;
|
||||
}
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/user/questions/index' });
|
||||
};
|
||||
|
||||
const noEvidenceText = (result: KnowledgeSearchResponse) => {
|
||||
if (result.agentIntent === 'WEB_RESEARCH') return '公开网络中没有找到可核验来源。';
|
||||
if (result.agentIntent === 'MEDIA_UNDERSTANDING') return '已分析附件,但当前资料中没有补充依据。';
|
||||
@@ -1437,7 +1483,9 @@ const nextActionLabel = (action: NextAction) => {
|
||||
|
||||
const nextActionDisabled = (action: NextAction, msg: MasterMsg) => {
|
||||
if (action.type === 'HUMAN_HELP') {
|
||||
return msg.helpSubmitting || msg.feedbackSubmitting || Boolean(msg.helpQuestionId) || !msg.result.requestId;
|
||||
// 已点“有帮助”再转人工必被服务端 409,直接禁用避免死路。
|
||||
return msg.helpSubmitting || msg.feedbackSubmitting || Boolean(msg.helpQuestionId) || !msg.result.requestId
|
||||
|| msg.feedbackVerdict === 'up';
|
||||
}
|
||||
if (action.type === 'DIRECT_CHANNEL') return !action.channel;
|
||||
return false;
|
||||
@@ -1447,6 +1495,7 @@ const nextActionButtonLabel = (action: NextAction, msg: MasterMsg) => {
|
||||
if (action.type === 'HUMAN_HELP') {
|
||||
if (msg.helpSubmitting || msg.feedbackSubmitting) return '提交中';
|
||||
if (msg.helpQuestionId) return '已提交审核';
|
||||
if (msg.feedbackVerdict === 'up') return '已反馈有帮助,如需求助请重新提问';
|
||||
}
|
||||
return nextActionLabel(action);
|
||||
};
|
||||
@@ -1516,6 +1565,7 @@ const submitAutoDownFeedback = async (msg: MasterMsg) => {
|
||||
/** nextActions 的 HUMAN_HELP 入口:未提交过 down 反馈时先自动补提交,再走既有确认弹窗 + 幂等提交流程。 */
|
||||
const submitHumanHelpFromNextAction = async (msg: MasterMsg) => {
|
||||
if (msg.helpSubmitting || msg.helpQuestionId || !msg.result.requestId) return;
|
||||
if (msg.feedbackVerdict === 'up') return;
|
||||
if (!msg.feedbackVerdict && !await submitAutoDownFeedback(msg)) return;
|
||||
await submitHumanHelp(msg);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user