feat: webAddress
This commit is contained in:
BIN
src/assets/images/link-outer.png
Normal file
BIN
src/assets/images/link-outer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 349 B |
@@ -1,92 +0,0 @@
|
|||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
export const useWebsiteTitle = () => {
|
|
||||||
const titles = ref<Map<string, string>>(new Map())
|
|
||||||
const loading = ref(new Set<string>())
|
|
||||||
const errors = ref(new Map<string, string>())
|
|
||||||
|
|
||||||
// 新增:重试配置
|
|
||||||
const MAX_RETRY = 3
|
|
||||||
const BASE_DELAY = 800 // 毫秒
|
|
||||||
|
|
||||||
const getCache = (url: string) => {
|
|
||||||
const cached = sessionStorage.getItem(`title_cache_${url}`)
|
|
||||||
if (!cached) return null
|
|
||||||
const { title, expire } = JSON.parse(cached)
|
|
||||||
return Date.now() < expire ? title : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const setCache = (url: string, title: string) => {
|
|
||||||
const data = { title, expire: Date.now() + 7 * 24 * 60 * 60 * 1000 }
|
|
||||||
sessionStorage.setItem(`title_cache_${url}`, JSON.stringify(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchWithRetry = async (url: string, retryCount = 0): Promise<string> => {
|
|
||||||
const proxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(proxyUrl)
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP ${res.status}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = await res.text()
|
|
||||||
const parser = new DOMParser()
|
|
||||||
const doc = parser.parseFromString(html, 'text/html')
|
|
||||||
const title =
|
|
||||||
doc.querySelector('title')?.textContent?.trim() || new URL(url).hostname || '无标题'
|
|
||||||
|
|
||||||
return title
|
|
||||||
} catch (err) {
|
|
||||||
if (retryCount >= MAX_RETRY) {
|
|
||||||
throw err // 达到最大重试次数,抛出错误
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第1次等800ms,第2次等1600ms,第3次等3200ms
|
|
||||||
const delay = BASE_DELAY * Math.pow(2, retryCount)
|
|
||||||
console.warn(
|
|
||||||
`获取标题失败,${retryCount + 1}/${MAX_RETRY} 次重试,等待 ${delay}ms`,
|
|
||||||
url
|
|
||||||
)
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
||||||
return fetchWithRetry(url, retryCount + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchTitle = async (url: string): Promise<string> => {
|
|
||||||
if (titles.value.has(url)) return titles.value.get(url)!
|
|
||||||
|
|
||||||
const cached = getCache(url)
|
|
||||||
if (cached) {
|
|
||||||
titles.value.set(url, cached)
|
|
||||||
return cached
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading.value.has(url)) return ''
|
|
||||||
|
|
||||||
loading.value.add(url)
|
|
||||||
errors.value.delete(url)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const title = await fetchWithRetry(url)
|
|
||||||
titles.value.set(url, title)
|
|
||||||
setCache(url, title)
|
|
||||||
return title
|
|
||||||
} catch (err) {
|
|
||||||
const msg = `获取标题失败(已重试 ${MAX_RETRY} 次)`
|
|
||||||
errors.value.set(url, msg)
|
|
||||||
console.error(err)
|
|
||||||
return msg
|
|
||||||
} finally {
|
|
||||||
loading.value.delete(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchAll = async (urls: string[]) => {
|
|
||||||
await Promise.allSettled(urls.map(fetchTitle))
|
|
||||||
}
|
|
||||||
|
|
||||||
return { titles, loading, errors, fetchTitle, fetchAll }
|
|
||||||
}
|
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
id: messageList.value.length + 1,
|
id: messageList.value.length + 1,
|
||||||
text: '',
|
text: '',
|
||||||
isUser: false,
|
isUser: false,
|
||||||
sessionId: 'projectStore.state.id',
|
sessionId: projectStore.state.id,
|
||||||
loading: true,
|
loading: true,
|
||||||
thinking: false,
|
thinking: false,
|
||||||
thinkingText: '',
|
thinkingText: '',
|
||||||
@@ -258,7 +258,7 @@
|
|||||||
reportsContent.value
|
reportsContent.value
|
||||||
) {
|
) {
|
||||||
isGeneratingReport.value = false
|
isGeneratingReport.value = false
|
||||||
localStorage.setItem(
|
sessionStorage.setItem(
|
||||||
'reportsContent_' + projectStore.state.id,
|
'reportsContent_' + projectStore.state.id,
|
||||||
reportsContent.value
|
reportsContent.value
|
||||||
)
|
)
|
||||||
@@ -297,6 +297,7 @@
|
|||||||
}
|
}
|
||||||
// if (eventName === 'webAddress') {
|
// if (eventName === 'webAddress') {
|
||||||
// console.log('webAddress111111111111111', eventName, dataLines)
|
// console.log('webAddress111111111111111', eventName, dataLines)
|
||||||
|
// debugger
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (eventName === 'tool') {
|
if (eventName === 'tool') {
|
||||||
@@ -311,17 +312,17 @@
|
|||||||
// console.log('jsonData', jsonData)
|
// console.log('jsonData', jsonData)
|
||||||
if (jsonData.webAddress) {
|
if (jsonData.webAddress) {
|
||||||
aiMessage.webAddress = JSON.parse(jsonData.webAddress)
|
aiMessage.webAddress = JSON.parse(jsonData.webAddress)
|
||||||
|
contentBody += `<slot slot-name="url">123</slot>`
|
||||||
}
|
}
|
||||||
if (jsonData.title) {
|
if (jsonData.title) {
|
||||||
emits('setTitle', jsonData.title)
|
emits('setTitle', jsonData.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSketch) {
|
if (hasSketch) {
|
||||||
console.log('sketch事件-----', jsonData);
|
|
||||||
|
|
||||||
sketchList.value.push({
|
sketchList.value.push({
|
||||||
[Object.keys(jsonData)[0]]: jsonData[Object.keys(jsonData)[0]]
|
[Object.keys(jsonData)[0]]: jsonData[Object.keys(jsonData)[0]]
|
||||||
})
|
})
|
||||||
|
MyEvent.emit('OpenSketch')
|
||||||
}
|
}
|
||||||
if (eventName === 'report') {
|
if (eventName === 'report') {
|
||||||
reportsContent.value += jsonData.report
|
reportsContent.value += jsonData.report
|
||||||
@@ -438,10 +439,6 @@
|
|||||||
|
|
||||||
while (i < dialogue.length) {
|
while (i < dialogue.length) {
|
||||||
const item = dialogue[i]
|
const item = dialogue[i]
|
||||||
if (item.webAddress?.length > 0) {
|
|
||||||
console.log('item.webAddress-----', item.webAddress)
|
|
||||||
debugger
|
|
||||||
}
|
|
||||||
if (item.role === 'user') {
|
if (item.role === 'user') {
|
||||||
// user 角色直接添加
|
// user 角色直接添加
|
||||||
result.push({
|
result.push({
|
||||||
@@ -513,7 +510,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (reportStr && session.id) {
|
if (reportStr && session.id) {
|
||||||
localStorage.setItem(`reportsContent_${session.id}`, reportStr)
|
sessionStorage.setItem(`reportsContent_${session.id}`, reportStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 收集 sketchIDAndUrl 到 imgList
|
// 3. 收集 sketchIDAndUrl 到 imgList
|
||||||
|
|||||||
@@ -40,11 +40,10 @@
|
|||||||
<template v-slot:s-card="{ children: children, ...attrs }">
|
<template v-slot:s-card="{ children: children, ...attrs }">
|
||||||
<Card :title="attrs.title" @click.native="handleClickReport" />
|
<Card :title="attrs.title" @click.native="handleClickReport" />
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:url-card="{ children: children }">
|
<template v-slot:s-url="{ children: children }">
|
||||||
<Url :list="content.webAddress" @click.native="handleClickUrls" />
|
<Url :list="content.webAddress" @click.native="handleClickUrls" />
|
||||||
</template>
|
</template>
|
||||||
</VueMarkdown>
|
</VueMarkdown>
|
||||||
<!-- <Url @click.native="handleClickUrls" /> -->
|
|
||||||
<div
|
<div
|
||||||
class="web-address flex align-center"
|
class="web-address flex align-center"
|
||||||
v-show="content.webAddress?.length > 0"
|
v-show="content.webAddress?.length > 0"
|
||||||
|
|||||||
@@ -50,6 +50,17 @@
|
|||||||
<img src="@/assets/images/sketch-loading.gif" alt="loading" />
|
<img src="@/assets/images/sketch-loading.gif" alt="loading" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="type === 'url'">
|
||||||
|
<div class="url-list flex">
|
||||||
|
<div class="url-item" v-for="item in urlList" :key="item">
|
||||||
|
<div class="url-title" @click="handleClickUrl(item)">
|
||||||
|
{{ item }}
|
||||||
|
<img src="@/assets/images/link-outer.png" class="link-outer" />
|
||||||
|
</div>
|
||||||
|
<div class="url-link">{{ item }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div v-else class="reportBorder">
|
<div v-else class="reportBorder">
|
||||||
<div class="report">
|
<div class="report">
|
||||||
<!-- <div v-if="false" class="report-content-null">
|
<!-- <div v-if="false" class="report-content-null">
|
||||||
@@ -75,11 +86,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
|
||||||
<div class="url-list">
|
|
||||||
<div class="url-item" v-for="item in urlList" :key="item"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,9 +105,6 @@
|
|||||||
import { VueMarkdown } from '@crazydos/vue-markdown'
|
import { VueMarkdown } from '@crazydos/vue-markdown'
|
||||||
import type { CustomAttrs } from '@crazydos/vue-markdown'
|
import type { CustomAttrs } from '@crazydos/vue-markdown'
|
||||||
import rehypeRaw from 'rehype-raw'
|
import rehypeRaw from 'rehype-raw'
|
||||||
import { useWebsiteTitle } from '@/utils/useWebTitle'
|
|
||||||
|
|
||||||
const { titles, loading, fetchAll } = useWebsiteTitle()
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const emits = defineEmits(['deleteSketch'])
|
const emits = defineEmits(['deleteSketch'])
|
||||||
@@ -111,7 +114,7 @@
|
|||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
type: 'sketch' | 'report'
|
type: 'sketch' | 'report' | 'url'
|
||||||
sketchList: Array<string>
|
sketchList: Array<string>
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
@@ -138,26 +141,23 @@
|
|||||||
const urlList = ref([])
|
const urlList = ref([])
|
||||||
const reportType = ref<'report' | 'urls'>('report')
|
const reportType = ref<'report' | 'urls'>('report')
|
||||||
const setSessionId = (id: string) => {
|
const setSessionId = (id: string) => {
|
||||||
console.log('setSessionId-----', id)
|
|
||||||
reportType.value = 'report'
|
reportType.value = 'report'
|
||||||
sessionId.value = id
|
sessionId.value = id
|
||||||
}
|
}
|
||||||
const setUrls = (list: string[]) => {
|
const setUrls = (list: string[]) => {
|
||||||
console.log('setUrls-----', list)
|
|
||||||
reportType.value = 'urls'
|
reportType.value = 'urls'
|
||||||
urlList.value = [
|
urlList.value = list
|
||||||
'https://furnitureindustrynews.substack.com/p/what-2026-really-looks-like-for-furniture',
|
// urlList.value = [
|
||||||
'https://furnitureindustrynews.substack.com/p/what-2026-really-looks-like-for-furniture',
|
// 'https://furnitureindustrynews.substack.com/p/what-2026-really-looks-like-for-furniture',
|
||||||
'https://furnitureindustrynews.substack.com/p/what-2026-really-looks-like-for-furniture'
|
// 'https://furnitureindustrynews.substack.com/p/what-2026-really-looks-like-for-furniture',
|
||||||
]
|
// 'https://furnitureindustrynews.substack.com/p/what-2026-really-looks-like-for-furniture'
|
||||||
fetchAll(list)
|
// ]
|
||||||
}
|
}
|
||||||
watch(
|
watch(
|
||||||
() => sessionId.value,
|
() => sessionId.value,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
markdownContent.value = localStorage.getItem(`reportsContent_${newVal}`)
|
markdownContent.value = sessionStorage.getItem(`reportsContent_${newVal}`)
|
||||||
console.log(`报告key值:reportsContent_${newVal}`, markdownContent.value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -226,6 +226,11 @@
|
|||||||
const handleLoadingSketch = () => {
|
const handleLoadingSketch = () => {
|
||||||
showLoading.value = true
|
showLoading.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClickUrl = (item: string) => {
|
||||||
|
window.open(item, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
// watch(
|
// watch(
|
||||||
// () => props.sketchList,
|
// () => props.sketchList,
|
||||||
// (val) => {
|
// (val) => {
|
||||||
@@ -301,6 +306,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.url-list {
|
||||||
|
flex: 1;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 4rem;
|
||||||
|
row-gap: 3rem;
|
||||||
|
.url-item {
|
||||||
|
width: 24rem;
|
||||||
|
height: 28.7rem;
|
||||||
|
word-break: break-all;
|
||||||
|
background: url('@/assets/images/web-card.png') no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
padding: 5rem 1.5rem;
|
||||||
|
.url-title {
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: 'Medium';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
color: #232323;
|
||||||
|
padding-bottom: 0.6rem;
|
||||||
|
.link-outer {
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.url-link {
|
||||||
|
font-family: 'Medium';
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #7c7c7c;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.reportBorder {
|
.reportBorder {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="report-card-content">
|
<div class="report-card-content">
|
||||||
<span v-if="!isUrl">{{ content }}</span>
|
<span v-if="!isUrl">markdown.md</span>
|
||||||
<span v-else>Destination URL</span>
|
<span v-else>Destination URL</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -104,16 +104,13 @@
|
|||||||
const proJectId = computed(() => route.params.id)
|
const proJectId = computed(() => route.params.id)
|
||||||
|
|
||||||
const handleOpenReport = (data, isUrls = false) => {
|
const handleOpenReport = (data, isUrls = false) => {
|
||||||
if (isUrls) {
|
previewRef.value.setSessionId(data)
|
||||||
previewRef.value.setUrls(data)
|
|
||||||
} else {
|
|
||||||
previewRef.value.setSessionId(data)
|
|
||||||
}
|
|
||||||
previewType.value = 'report'
|
previewType.value = 'report'
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOpenUrls = (data) => {
|
const handleOpenUrls = (data) => {
|
||||||
handleOpenReport(data, true)
|
previewRef.value.setUrls(data)
|
||||||
|
previewType.value = 'url'
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -129,6 +126,9 @@
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
MyEvent.add('openReport', handleOpenReport)
|
MyEvent.add('openReport', handleOpenReport)
|
||||||
MyEvent.add('openUrls', handleOpenUrls)
|
MyEvent.add('openUrls', handleOpenUrls)
|
||||||
|
MyEvent.add('OpenSketch', ()=>{
|
||||||
|
previewType.value = 'sketch'
|
||||||
|
})
|
||||||
projectStore.clearProject()
|
projectStore.clearProject()
|
||||||
if (proJectId.value) {
|
if (proJectId.value) {
|
||||||
handleGetProjectInfoAndHistory()
|
handleGetProjectInfoAndHistory()
|
||||||
@@ -137,6 +137,9 @@
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
MyEvent.remove('openReport', handleOpenReport)
|
MyEvent.remove('openReport', handleOpenReport)
|
||||||
MyEvent.remove('openUrls', handleOpenUrls)
|
MyEvent.remove('openUrls', handleOpenUrls)
|
||||||
|
MyEvent.remove('OpenSketch', ()=>{
|
||||||
|
previewType.value = 'sketch'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user