diff --git a/.DS_Store b/.DS_Store
index 3f32ead..03df16f 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a503fa2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/unpackage/
diff --git a/App.vue b/App.vue
index 6151e8d..c2bd362 100644
--- a/App.vue
+++ b/App.vue
@@ -1,9 +1,9 @@
-
-
+
+ -->
diff --git a/pages.json b/pages.json
index e14a41a..dff83c7 100644
--- a/pages.json
+++ b/pages.json
@@ -120,14 +120,14 @@
]
}],
"tabBar": {
- "color": "#7A7E83",
+ "color": "#5E5F60",
"selectedColor": "#256BFA",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"midButton": {
"width": "50px",
"height": "50px",
- "backgroundImage": "static/tabbar/logo2.png"
+ "backgroundImage": "static/tabbar/logo2copy.png"
},
"list": [{
"pagePath": "pages/index/index",
@@ -143,8 +143,8 @@
},
{
"pagePath": "pages/chat/chat",
- "iconPath": "static/tabbar/logo2.png",
- "selectedIconPath": "static/tabbar/logo2.png"
+ "iconPath": "static/tabbar/logo2copy.png",
+ "selectedIconPath": "static/tabbar/logo2copy.png"
},
{
"pagePath": "pages/msglog/msglog",
diff --git a/pages/chat/chat.vue b/pages/chat/chat.vue
index 24397b2..17e9db0 100644
--- a/pages/chat/chat.vue
+++ b/pages/chat/chat.vue
@@ -37,9 +37,14 @@
-
- 用户123
-
+
+
+ {{ userInfo.name || '暂无用户名' }}
+
@@ -64,11 +69,13 @@
diff --git a/pages/chat/components/ai-paging.vue b/pages/chat/components/ai-paging.vue
index 4a86070..44b569c 100644
--- a/pages/chat/components/ai-paging.vue
+++ b/pages/chat/components/ai-paging.vue
@@ -8,8 +8,12 @@
我可以根据您的简历和求职需求,帮你精准匹配青岛市互联网招聘信息,对比招聘信息的优缺点,提供面试指导等,请把你的任务交给我吧~
猜你所想
- 我希望找青岛的IT行业岗位,薪资能否在12000以上?
- 我有三年的工作经验,能否推荐一些适合我的青岛的国企 岗位?
+
+ {{ item }}
+
+
+ {{ recognizedText }} {{ lastFinalText }}
+
@@ -25,7 +29,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ recognizedText }} {{ lastFinalText }}
+
@@ -70,7 +109,12 @@
{{ statusText }}
-
+
@@ -82,7 +126,7 @@
:disabled="isTyping"
:adjust-position="false"
placeholder="请输入您的职位名称、薪资要求、岗位地址"
- v-if="!isVoice"
+ v-show="!isVoice"
/>
按住说话
@@ -114,8 +158,8 @@
-
-
+
+
@@ -153,15 +197,19 @@
:src="file.url"
>
-
+
+
{{ file.name }}
- {{ file.size }}
+
+
+
+ {{ file.size }}
+
-
-
+
@@ -176,20 +224,36 @@ import { ref, inject, nextTick, defineProps, defineEmits, onMounted, toRaw, reac
import { storeToRefs } from 'pinia';
import config from '@/config.js';
import useChatGroupDBStore from '@/stores/userChatGroupStore';
-const { $api, navTo, throttle } = inject('globalFunction');
-const emit = defineEmits(['onConfirm']);
import MdRender from '@/components/md-render/md-render.vue';
-const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
import CollapseTransition from '@/components/CollapseTransition/CollapseTransition.vue';
import FadeView from '@/components/FadeView/FadeView.vue';
import AudioWave from './AudioWave.vue';
+import WaveDisplay from './WaveDisplay.vue';
import FileIcon from './fileIcon.vue';
+import FileText from './fileText.vue';
+import { useSpeechReader } from '@/hook/useSpeechReader';
import { useAudioRecorder } from '@/hook/useRealtimeRecorder.js';
+// 全局
+const { $api, navTo, throttle } = inject('globalFunction');
+const emit = defineEmits(['onConfirm']);
+const { messages, isTyping, textInput, chatSessionID } = storeToRefs(useChatGroupDBStore());
-const { isRecording, recognizedText, startRecording, stopRecording, cancelRecording } = useAudioRecorder(
- config.vioceBaseURl
-);
+// hook
+const {
+ isRecording,
+ startRecording,
+ stopRecording,
+ cancelRecording,
+ audioDataForDisplay,
+ volumeLevel,
+ recognizedText,
+ lastFinalText,
+} = useAudioRecorder(config.vioceBaseURl);
+const { speak, pause, resume, isSpeaking, isPaused } = useSpeechReader();
+
+// state
+const queries = ref([]);
const guessList = ref([]);
const scrollTop = ref(0);
const showGuess = ref(false);
@@ -200,18 +264,62 @@ const isVoice = ref(false);
const status = ref('idle'); // idle | recording | cancel
const startY = ref(0);
const cancelThreshold = 100;
-let recordingTimer = null;
+const speechIndex = ref(0);
+const isAudioPermission = ref(false);
const state = reactive({
uploadFileTips: '请根据以上附件,帮我推荐岗位。',
});
-onMounted(() => {
- scrollToBottom();
+const statusText = computed(() => {
+ switch (status.value) {
+ case 'recording':
+ return '松手发送,上划取消';
+ case 'cancel':
+ return '松手取消';
+ default:
+ return '按住说话';
+ }
});
-const sendMessage = () => {
- const values = textInput.value;
+const audiowaveStyle = computed(() => {
+ return status.value === 'cancel'
+ ? '#f54545'
+ : status.value === 'recording'
+ ? 'linear-gradient(to right, #377dff, #9a60ff)'
+ : '#f1f1f1';
+});
+
+onMounted(async () => {
+ changeQueries();
+ scrollToBottom();
+ isAudioPermission.value = await requestMicPermission();
+});
+
+const requestMicPermission = async () => {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ console.log('✅ 麦克风权限已授权');
+
+ // 立刻停止所有音轨,释放麦克风
+ stream.getTracks().forEach((track) => track.stop());
+
+ return true;
+ } catch (err) {
+ console.warn('❌ 用户拒绝麦克风权限或不支持:', err);
+ return false;
+ }
+};
+
+function showControll(index) {
+ if (index === messages.value.length - 1 && isTyping.value) {
+ return false;
+ }
+ return true;
+}
+
+const sendMessage = (text) => {
+ const values = textInput.value || text;
showfile.value = false;
showGuess.value = false;
if (values.trim()) {
@@ -222,7 +330,9 @@ const sendMessage = () => {
const newMsg = { text: values, self: true, displayText: values, files: normalArr };
useChatGroupDBStore().addMessage(newMsg);
useChatGroupDBStore()
- .getStearm(values, normalArr, scrollToBottom)
+ .getStearm(values, normalArr, scrollToBottom, {
+ onComplete: () => console.log('Display complete'),
+ })
.then(() => {
getGuess();
scrollToBottom();
@@ -276,19 +386,17 @@ const delfile = (file) => {
const scrollToBottom = throttle(function () {
nextTick(() => {
try {
- setTimeout(() => {
- const query = uni.createSelectorQuery();
- query.select('.scrollView').boundingClientRect();
- query.select('.list-content').boundingClientRect();
- query.exec((res) => {
- const scrollViewHeight = res[0].height;
- const scrollContentHeight = res[1].height;
- if (scrollContentHeight > scrollViewHeight) {
- const scrolldistance = scrollContentHeight - scrollViewHeight;
- scrollTop.value = scrolldistance;
- }
- });
- }, 500);
+ const query = uni.createSelectorQuery();
+ query.select('.scrollView').boundingClientRect();
+ query.select('.list-content').boundingClientRect();
+ query.exec((res) => {
+ const scrollViewHeight = res[0].height;
+ const scrollContentHeight = res[1].height;
+ if (scrollContentHeight > scrollViewHeight) {
+ const scrolldistance = scrollContentHeight - scrollViewHeight;
+ scrollTop.value = scrolldistance;
+ }
+ });
} catch (err) {
console.warn(err);
}
@@ -386,14 +494,29 @@ function getUploadFile(type = 'camera') {
});
}
-const handleTouchStart = (e) => {
+const tipsPermisson = () => {
+ uni.showToast({
+ title: '需要授权麦克风权限才能使用语音功能',
+ icon: 'none',
+ });
+};
+
+const handleTouchStart = async (e) => {
+ if (!isAudioPermission.value) {
+ return tipsPermisson();
+ }
+ console.log('handleTouchStart');
startY.value = e.touches[0].clientY;
status.value = 'recording';
showfile.value = false;
startRecording();
+ $api.sleep(1000).then(() => {
+ scrollToBottom();
+ });
};
const handleTouchMove = (e) => {
+ console.log('handleTouchMove');
const moveY = e.touches[0].clientY;
if (startY.value - moveY > cancelThreshold) {
status.value = 'cancel';
@@ -403,40 +526,32 @@ const handleTouchMove = (e) => {
};
const handleTouchEnd = () => {
+ console.log('handleTouchEnd');
if (status.value === 'cancel') {
console.log('取消发送');
cancelRecording();
} else {
+ console.log('stopRecording');
stopRecording();
- console.log('发送语音');
+ $api.sleep(1000).then(() => {
+ if (isAudioPermission.value) {
+ if (recognizedText.value) {
+ sendMessage(recognizedText.value);
+ } else {
+ $api.msg('说话时长太短');
+ }
+ }
+ });
}
status.value = 'idle';
};
const handleTouchCancel = () => {
+ console.log('handleTouchCancel');
stopRecording();
status.value = 'idle';
};
-const statusText = computed(() => {
- switch (status.value) {
- case 'recording':
- return '松手发送,上划取消';
- case 'cancel':
- return '松手取消';
- default:
- return '按住说话';
- }
-});
-
-const audiowaveStyle = computed(() => {
- return status.value === 'cancel'
- ? '#f54545'
- : status.value === 'recording'
- ? 'linear-gradient(to right, #377dff, #9a60ff)'
- : '#f1f1f1';
-});
-
function closeGuess() {
showGuess.value = false;
}
@@ -452,7 +567,74 @@ function changeShowFile() {
function colseFile() {
showfile.value = false;
}
-defineExpose({ scrollToBottom, closeGuess, colseFile });
+
+function copyMarkdown(value) {
+ $api.copyText(value);
+}
+
+function userGoodFeedback(msg) {
+ $api.msg('该功能正在开发中,敬请期待后续更新!');
+ // const params = {
+ // dataId: msg.dataId,
+ // sessionId: msg.parentGroupId,
+ // userGoodFeedback: 'no',
+ // };
+}
+
+function readMarkdown(value, index) {
+ speechIndex.value = index;
+ if (speechIndex.value !== index) {
+ speak(value);
+ return;
+ }
+ if (isPaused.value) {
+ resume();
+ } else {
+ speak(value);
+ }
+}
+function stopMarkdown(value, index) {
+ pause(value);
+ speechIndex.value = index;
+}
+function refreshMarkdown(index) {
+ if (isTyping.value) {
+ $api.msg('正在生成');
+ } else {
+ const text = messages.value[index - 1].text;
+ sendMessageGuess(text);
+ }
+}
+
+const jobSearchQueries = [
+ '青岛有哪些薪资 12K 以上的岗位适合我?',
+ '青岛 3 年工作经验能找到哪些 12K 以上的工作?',
+ '青岛哪些公司在招聘,薪资范围在 12K 以上?',
+ '青岛有哪些企业提供 15K 以上的岗位?',
+ '青岛哪些公司在招 3-5 年经验的岗位?',
+ '我有三年的工作经验,能否推荐一些适合我的青岛的国企 岗位?',
+ '青岛国企目前在招聘哪些岗位?',
+ '青岛有哪些适合 3 年经验的国企岗位?',
+ '青岛国企招聘的岗位待遇如何?',
+ '青岛国企岗位的薪资水平是多少?',
+ '青岛哪些国企支持双休 & 五险一金完善?',
+ '青岛有哪些公司支持远程办公?',
+ '青岛有哪些外企的岗位,薪资 12K 以上的多吗?',
+ '青岛哪些企业在招聘 Web3.0 相关岗位?',
+ '青岛哪些岗位支持海外远程?薪资如何?',
+ '青岛招聘 AI/大数据相关岗位的公司有哪些?',
+];
+
+function changeQueries(value) {
+ queries.value = getRandomJobQueries(jobSearchQueries);
+}
+
+function getRandomJobQueries(queries, count = 2) {
+ const shuffled = queries.sort(() => 0.5 - Math.random()); // 随机打乱数组
+ return shuffled.slice(0, count); // 取前 count 条
+}
+
+defineExpose({ scrollToBottom, closeGuess, colseFile, changeQueries, handleTouchCancel });
diff --git a/pages/chat/components/fileText.vue b/pages/chat/components/fileText.vue
new file mode 100644
index 0000000..39e6a0d
--- /dev/null
+++ b/pages/chat/components/fileText.vue
@@ -0,0 +1,38 @@
+
+
+ {{ fileAbbreviation }}
+
+
+
+
+
+
diff --git a/pages/index/index.vue b/pages/index/index.vue
index 82fb777..a5efb62 100644
--- a/pages/index/index.vue
+++ b/pages/index/index.vue
@@ -328,9 +328,9 @@ function getJobList(type = 'add') {
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
if (rows.length < pageState.pageSize) {
- loadmoreRef.value.change('noMore');
+ loadmoreRef.value?.change('noMore');
} else {
- loadmoreRef.value.change('more');
+ loadmoreRef.value?.change('more');
}
});
}
diff --git a/pages/mine/mine.vue b/pages/mine/mine.vue
index 83897d1..0ffb011 100644
--- a/pages/mine/mine.vue
+++ b/pages/mine/mine.vue
@@ -56,16 +56,12 @@
- 青岛智慧就业服务
-
-
-
-
-
-
-
-
-
-
-