Skip to content

Commit 304b5f5

Browse files
feat: Mobile devices allow users to provide reasons when liking or disliking a reply.
1 parent 0f30955 commit 304b5f5

File tree

8 files changed

+276
-116
lines changed

8 files changed

+276
-116
lines changed

ui/src/components/ai-chat/component/operation-button/ChatOperationButton.vue

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,36 @@
5252
</el-button>
5353
</el-tooltip>
5454
<el-divider direction="vertical" />
55+
56+
<el-tooltip
57+
v-if="buttonData?.vote_status === '-1' && mode === 'mobile'"
58+
effect="dark"
59+
:content="$t('chat.operation.like')"
60+
placement="top"
61+
>
62+
<el-button text :disabled="loading" @click="mobileVoteReasonHandler('0')">
63+
<AppIcon class="color-secondary" iconName="app-like"></AppIcon>
64+
</el-button>
65+
</el-tooltip>
66+
5567
<el-popover
5668
ref="likePopoverRef"
5769
trigger="click"
5870
placement="bottom-start"
5971
:width="360"
6072
popper-class="vote-popover"
73+
v-if="buttonData?.vote_status === '-1' && mode !== 'mobile'"
6174
>
6275
<template #reference>
6376
<span>
64-
<el-tooltip
65-
effect="dark"
66-
:content="$t('chat.operation.like')"
67-
placement="top"
68-
v-if="buttonData?.vote_status === '-1'"
69-
>
77+
<el-tooltip effect="dark" :content="$t('chat.operation.like')" placement="top">
7078
<el-button text :disabled="loading">
7179
<AppIcon class="color-secondary" iconName="app-like"></AppIcon>
7280
</el-button>
7381
</el-tooltip>
7482
</span>
7583
</template>
7684
<VoteReasonContent
77-
v-if="props.data.record_id"
7885
vote-type="0"
7986
:chat-id="props.chatId"
8087
:record-id="props.data.record_id"
@@ -95,29 +102,34 @@
95102
</el-button>
96103
</el-tooltip>
97104
<el-divider direction="vertical" v-if="buttonData?.vote_status === '-1'" />
105+
<el-tooltip
106+
v-if="buttonData?.vote_status === '-1' && mode === 'mobile'"
107+
effect="dark"
108+
:content="$t('chat.operation.oppose')"
109+
placement="top"
110+
>
111+
<el-button text :disabled="loading" @click="mobileVoteReasonHandler('1')">
112+
<AppIcon class="color-secondary" iconName="app-oppose"></AppIcon>
113+
</el-button>
114+
</el-tooltip>
98115
<el-popover
99116
ref="opposePopoverRef"
100117
trigger="click"
101118
placement="bottom-start"
102119
:width="360"
103120
popper-class="vote-popover"
121+
v-if="buttonData?.vote_status === '-1' && mode !== 'mobile'"
104122
>
105123
<template #reference>
106124
<span>
107-
<el-tooltip
108-
effect="dark"
109-
:content="$t('chat.operation.oppose')"
110-
placement="top"
111-
v-if="buttonData?.vote_status === '-1'"
112-
>
125+
<el-tooltip effect="dark" :content="$t('chat.operation.oppose')" placement="top">
113126
<el-button text :disabled="loading">
114127
<AppIcon class="color-secondary" iconName="app-oppose"></AppIcon>
115128
</el-button>
116129
</el-tooltip>
117130
</span>
118131
</template>
119132
<VoteReasonContent
120-
v-if="props.data.record_id"
121133
vote-type="1"
122134
:chat-id="props.chatId"
123135
:record-id="props.data.record_id"
@@ -139,6 +151,12 @@
139151
</span>
140152
<div ref="audioCiontainer"></div>
141153
</div>
154+
<MobileVoteReasonDrawer
155+
ref="mobileVoteReasonDrawerRef"
156+
:chat-id="props.chatId"
157+
:record-id="props.data.record_id"
158+
@success="handleVoteSuccess"
159+
/>
142160
</div>
143161
</template>
144162
<script setup lang="ts">
@@ -150,26 +168,13 @@ import chatAPI from '@/api/chat/chat'
150168
import { datetimeFormat } from '@/utils/time'
151169
import { MsgError } from '@/utils/message'
152170
import VoteReasonContent from '@/components/ai-chat/component/operation-button/VoteReasonContent.vue'
171+
import MobileVoteReasonDrawer from '@/components/ai-chat/component/operation-button/MobileVoteReasonDrawer.vue'
153172
import bus from '@/bus'
154-
const copy = (data: any) => {
155-
try {
156-
const text = data.answer_text_list
157-
.map((item: Array<any>) => item.map((i) => i.content).join('\n'))
158-
.join('\n\n')
159-
copyClick(removeFormRander(text))
160-
} catch (e: any) {
161-
copyClick(removeFormRander(data?.answer_text.trim()))
162-
}
163-
}
164-
const likePopoverRef = ref()
165-
const opposePopoverRef = ref()
166-
const closePopover = () => {
167-
likePopoverRef.value.hide()
168-
opposePopoverRef.value.hide()
169-
}
173+
170174
const route = useRoute()
171175
const {
172176
params: { id },
177+
query: { mode },
173178
} = route as any
174179
175180
const props = withDefaults(
@@ -191,6 +196,30 @@ const props = withDefaults(
191196
192197
const emit = defineEmits(['update:data', 'regeneration'])
193198
199+
const copy = (data: any) => {
200+
try {
201+
const text = data.answer_text_list
202+
.map((item: Array<any>) => item.map((i) => i.content).join('\n'))
203+
.join('\n\n')
204+
copyClick(removeFormRander(text))
205+
} catch (e: any) {
206+
copyClick(removeFormRander(data?.answer_text.trim()))
207+
}
208+
}
209+
210+
const likePopoverRef = ref()
211+
const opposePopoverRef = ref()
212+
const closePopover = () => {
213+
likePopoverRef.value.hide()
214+
opposePopoverRef.value.hide()
215+
}
216+
const mobileVoteReasonDrawerRef = ref<InstanceType<typeof MobileVoteReasonDrawer> | null>(null)
217+
const mobileVoteReasonHandler = (voteStatus: string) => {
218+
if (mobileVoteReasonDrawerRef.value) {
219+
mobileVoteReasonDrawerRef.value.open(voteStatus)
220+
}
221+
}
222+
194223
const audioPlayer = ref<HTMLAudioElement[] | null>([])
195224
const audioCiontainer = ref<HTMLDivElement>()
196225
const buttonData = ref(props.data)
@@ -204,7 +233,9 @@ function regeneration() {
204233
function handleVoteSuccess(voteStatus: string) {
205234
buttonData.value['vote_status'] = voteStatus
206235
emit('update:data', buttonData.value)
207-
closePopover()
236+
if (mode !== 'mobile') {
237+
closePopover()
238+
}
208239
}
209240
210241
function cancelVoteHandle(val: string) {
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<template>
2+
<el-drawer v-model="visible" direction="btt" size="-" footer-class="mobile-vote-drawer-footer" :modal="true">
3+
<template #header>
4+
<h4 class="text-center">{{ title }}</h4>
5+
</template>
6+
<template #default>
7+
<el-space wrap :size="12">
8+
<template v-for="reason in reasons" :key="reason.value">
9+
<el-check-tag
10+
type="primary"
11+
:checked="selectedReason === reason.value"
12+
@change="selectReason(reason.value)"
13+
>
14+
{{ reason.label }}</el-check-tag
15+
>
16+
</template>
17+
</el-space>
18+
19+
<div v-if="selectedReason === 'other'" class="mt-16">
20+
<el-input
21+
v-model="feedBack"
22+
type="textarea"
23+
:autosize="{ minRows: 4, maxRows: 20 }"
24+
:placeholder="$t('chat.vote.placeholder')"
25+
>
26+
</el-input>
27+
</div>
28+
</template>
29+
<template #footer>
30+
<el-space fill wrap :fill-ratio="40" style="width: 100%">
31+
<el-button @click="visible = false"> {{ $t('common.cancel') }}</el-button>
32+
<el-button :disabled="isSubmitDisabled" type="primary" @click="voteHandle()">
33+
{{ $t('common.submit') }}</el-button
34+
>
35+
</el-space>
36+
</template>
37+
</el-drawer>
38+
</template>
39+
40+
<script setup lang="ts">
41+
import { ref, computed } from 'vue'
42+
import { t } from '@/locales'
43+
import chatAPI from '@/api/chat/chat'
44+
45+
const props = defineProps<{
46+
chatId: string
47+
recordId: string
48+
defaultReason?: string
49+
defaultOtherContent?: string
50+
}>()
51+
52+
const visible = ref(false)
53+
const voteType = ref<string>('') // '0' like, '1' oppose
54+
const selectedReason = ref<string>('')
55+
const feedBack = ref<string>('')
56+
const loading = ref(false)
57+
58+
const selectReason = (value: string) => {
59+
selectedReason.value = value
60+
}
61+
62+
const isSubmitDisabled = computed(() => {
63+
if (!selectedReason.value) {
64+
return true
65+
}
66+
if (selectedReason.value === 'other' && !feedBack.value.trim()) {
67+
return true
68+
}
69+
return false
70+
})
71+
72+
const LIKE_REASONS = [
73+
{ label: t('chat.vote.accurate'), value: 'accurate' },
74+
{ label: t('chat.vote.complete'), value: 'complete' },
75+
{ label: t('chat.vote.other'), value: 'other' },
76+
]
77+
78+
const OPPOSE_REASONS = [
79+
{ label: t('chat.vote.inaccurate'), value: 'inaccurate' },
80+
{ label: t('chat.vote.irrelevantAnswer'), value: 'incomplete' },
81+
{ label: t('chat.vote.other'), value: 'other' },
82+
]
83+
84+
const title = computed(() => {
85+
return voteType.value === '0' ? t('chat.vote.likeTitle') : t('chat.vote.opposeTitle')
86+
})
87+
88+
const reasons = computed(() => {
89+
return voteType.value === '0' ? LIKE_REASONS : OPPOSE_REASONS
90+
})
91+
92+
function voteHandle() {
93+
chatAPI
94+
.vote(
95+
props.chatId,
96+
props.recordId,
97+
voteType.value,
98+
selectedReason.value,
99+
feedBack.value,
100+
loading,
101+
)
102+
.then(() => {
103+
emit('success', voteType.value)
104+
visible.value = false
105+
})
106+
}
107+
108+
const emit = defineEmits<{
109+
success: [voteStatus: string]
110+
}>()
111+
112+
const open = (voteStatus: string) => {
113+
selectedReason.value = ''
114+
feedBack.value = ''
115+
voteType.value = voteStatus
116+
visible.value = true
117+
}
118+
119+
defineExpose({ open })
120+
</script>
121+
122+
<style lang="scss">
123+
.mobile-vote-drawer-footer {
124+
padding: 0 24px 32px 24px;
125+
border: none!important;
126+
}
127+
</style>

ui/src/components/ai-chat/component/operation-button/VoteReasonContent.vue

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
11
<template>
2-
<h4>{{ title }}</h4>
3-
<div class="mt-16">
4-
<el-space wrap :size="12">
5-
<template v-for="reason in reasons" :key="reason.value">
6-
<el-check-tag
7-
type="primary"
8-
:checked="selectedReason === reason.value"
9-
@change="selectReason(reason.value)"
10-
11-
>
12-
{{ reason.label }}</el-check-tag
13-
>
14-
</template>
15-
</el-space>
16-
</div>
17-
<div v-if="selectedReason === 'other'" class="mt-16">
18-
<el-input
19-
v-model="feedBack"
20-
type="textarea"
21-
:autosize="{ minRows: 4, maxRows: 20 }"
22-
:placeholder="$t('chat.vote.placeholder')"
23-
:readonly="readonly"
24-
>
25-
</el-input>
26-
</div>
27-
<div v-if="!readonly" class="dialog-footer mt-24 text-right">
28-
<el-button @click="emit('close')"> {{ $t('common.cancel') }}</el-button>
29-
<el-button :disabled="isSubmitDisabled" type="primary" @click="voteHandle()">
30-
{{ $t('common.submit') }}</el-button
31-
>
2+
<div>
3+
<h4>{{ title }}</h4>
4+
<div class="mt-16">
5+
<el-space wrap :size="12">
6+
<template v-for="reason in reasons" :key="reason.value">
7+
<el-check-tag
8+
type="primary"
9+
:checked="selectedReason === reason.value"
10+
@change="selectReason(reason.value)"
11+
>
12+
{{ reason.label }}</el-check-tag
13+
>
14+
</template>
15+
</el-space>
16+
</div>
17+
<div v-if="selectedReason === 'other'" class="mt-16">
18+
<el-input
19+
v-model="feedBack"
20+
type="textarea"
21+
:autosize="{ minRows: 4, maxRows: 20 }"
22+
:placeholder="$t('chat.vote.placeholder')"
23+
:readonly="readonly"
24+
>
25+
</el-input>
26+
</div>
27+
<div v-if="!readonly" class="dialog-footer mt-24 text-right">
28+
<el-button @click="emit('close')"> {{ $t('common.cancel') }}</el-button>
29+
<el-button :disabled="isSubmitDisabled" type="primary" @click="voteHandle()">
30+
{{ $t('common.submit') }}</el-button
31+
>
32+
</div>
3233
</div>
3334
</template>
3435

ui/src/components/ai-chat/index.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
position: absolute;
1313
right: 14px;
1414
top: -30px;
15+
z-index: 22;
1516
}
1617
&__content {
1718
padding-top: 0;
@@ -42,7 +43,7 @@
4243

4344
@media only screen and (max-width: 768px) {
4445
.ai-chat {
45-
height: calc(100% - 106px) !important;
46+
height: calc(100% - 116px) !important;
4647
}
4748
}
4849
.chat-mobile {

ui/src/views/application/template-store/TemplateStoreDialog.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
{{ t('views.tool.toolStore.searchResult', { count: filterList.length }) }}
7676
</h4> -->
7777
<el-row :gutter="16" v-if="filterList.length">
78-
<el-col v-for="tool in filterList" :key="tool.id" :span="12" class="mb-16">
78+
<el-col v-for="tool in filterList" :key="tool.id" :span="8" class="mb-16">
7979
<TemplateCard
8080
:tool="tool"
8181
:addLoading="addLoading"

ui/src/views/chat/embed/index.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,5 @@ onMounted(() => {
312312
}
313313
</style>
314314
<style lang="scss" scoped>
315-
:deep(.el-overlay) {
316-
background-color: transparent;
317-
}
315+
318316
</style>

0 commit comments

Comments
 (0)