fix: #2957 improve topic auto renaming & remove special characters from file name when topic exported (#3132)

* fix: refine special character removal for topic auto renaming #2957

* fix: remove special characters in topic title when used as file name #2957
This commit is contained in:
Chuqiao Feng 2025-03-10 17:09:02 +08:00 committed by GitHub
parent 3790e82ef3
commit 8a0a109fb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 28 additions and 10 deletions

View File

@ -18,7 +18,12 @@ import {
updateMessages updateMessages
} from '@renderer/store/messages' } from '@renderer/store/messages'
import type { Assistant, Message, Topic } from '@renderer/types' import type { Assistant, Message, Topic } from '@renderer/types'
import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, runAsyncFunction } from '@renderer/utils' import {
captureScrollableDivAsBlob,
captureScrollableDivAsDataURL,
removeSpecialCharactersForFileName,
runAsyncFunction
} from '@renderer/utils'
import { isEmpty, last } from 'lodash' import { isEmpty, last } from 'lodash'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -154,7 +159,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic })
EventEmitter.on(EVENT_NAMES.EXPORT_TOPIC_IMAGE, async () => { EventEmitter.on(EVENT_NAMES.EXPORT_TOPIC_IMAGE, async () => {
const imageData = await captureScrollableDivAsDataURL(containerRef) const imageData = await captureScrollableDivAsDataURL(containerRef)
if (imageData) { if (imageData) {
window.api.file.saveImage(topic.name, imageData) window.api.file.saveImage(removeSpecialCharactersForFileName(topic.name), imageData)
} }
}), }),
EventEmitter.on(EVENT_NAMES.NEW_CONTEXT, async () => { EventEmitter.on(EVENT_NAMES.NEW_CONTEXT, async () => {

View File

@ -35,6 +35,7 @@ import { findIndex } from 'lodash'
import { FC, useCallback, useRef, useState } from 'react' import { FC, useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import { removeSpecialCharactersForFileName } from '@renderer/utils'
interface Props { interface Props {
assistant: Assistant assistant: Assistant
@ -234,7 +235,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
key: 'word', key: 'word',
onClick: async () => { onClick: async () => {
const markdown = await topicToMarkdown(topic) const markdown = await topicToMarkdown(topic)
window.api.export.toWord(markdown, topic.name) window.api.export.toWord(markdown, removeSpecialCharactersForFileName(topic.name))
} }
}, },
{ {

View File

@ -13,7 +13,7 @@ import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@rende
import { EVENT_NAMES } from '@renderer/services/EventService' import { EVENT_NAMES } from '@renderer/services/EventService'
import { filterContextMessages, filterUserRoleStartMessages } from '@renderer/services/MessagesService' import { filterContextMessages, filterUserRoleStartMessages } from '@renderer/services/MessagesService'
import { Assistant, FileTypes, MCPToolResponse, Message, Model, Provider, Suggestion } from '@renderer/types' import { Assistant, FileTypes, MCPToolResponse, Message, Model, Provider, Suggestion } from '@renderer/types'
import { removeSpecialCharacters } from '@renderer/utils' import { removeSpecialCharactersForTopicName } from '@renderer/utils'
import { first, flatten, sum, takeRight } from 'lodash' import { first, flatten, sum, takeRight } from 'lodash'
import OpenAI from 'openai' import OpenAI from 'openai'
@ -409,7 +409,7 @@ export default class AnthropicProvider extends BaseProvider {
const content = message.content[0].type === 'text' ? message.content[0].text : '' const content = message.content[0].type === 'text' ? message.content[0].text : ''
return removeSpecialCharacters(content) return removeSpecialCharactersForTopicName(content)
} }
public async generateText({ prompt, content }: { prompt: string; content: string }): Promise<string> { public async generateText({ prompt, content }: { prompt: string; content: string }): Promise<string> {

View File

@ -20,7 +20,7 @@ import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@rende
import { EVENT_NAMES } from '@renderer/services/EventService' import { EVENT_NAMES } from '@renderer/services/EventService'
import { filterContextMessages, filterUserRoleStartMessages } from '@renderer/services/MessagesService' import { filterContextMessages, filterUserRoleStartMessages } from '@renderer/services/MessagesService'
import { Assistant, FileType, FileTypes, MCPToolResponse, Message, Model, Provider, Suggestion } from '@renderer/types' import { Assistant, FileType, FileTypes, MCPToolResponse, Message, Model, Provider, Suggestion } from '@renderer/types'
import { removeSpecialCharacters } from '@renderer/utils' import { removeSpecialCharactersForTopicName } from '@renderer/utils'
import axios from 'axios' import axios from 'axios'
import { isEmpty, takeRight } from 'lodash' import { isEmpty, takeRight } from 'lodash'
import OpenAI from 'openai' import OpenAI from 'openai'
@ -379,7 +379,7 @@ export default class GeminiProvider extends BaseProvider {
const { response } = await chat.sendMessage(userMessage.content) const { response } = await chat.sendMessage(userMessage.content)
return removeSpecialCharacters(response.text()) return removeSpecialCharactersForTopicName(response.text())
} }
public async generateText({ prompt, content }: { prompt: string; content: string }): Promise<string> { public async generateText({ prompt, content }: { prompt: string; content: string }): Promise<string> {

View File

@ -21,7 +21,7 @@ import {
Provider, Provider,
Suggestion Suggestion
} from '@renderer/types' } from '@renderer/types'
import { removeSpecialCharacters } from '@renderer/utils' import { removeSpecialCharactersForTopicName } from '@renderer/utils'
import { takeRight } from 'lodash' import { takeRight } from 'lodash'
import OpenAI, { AzureOpenAI } from 'openai' import OpenAI, { AzureOpenAI } from 'openai'
import { import {
@ -575,7 +575,7 @@ export default class OpenAIProvider extends BaseProvider {
let content = response.choices[0].message?.content || '' let content = response.choices[0].message?.content || ''
content = content.replace(/^<think>(.*?)<\/think>/s, '') content = content.replace(/^<think>(.*?)<\/think>/s, '')
return removeSpecialCharacters(content.substring(0, 50)) return removeSpecialCharactersForTopicName(content.substring(0, 50))
} }
public async generateText({ prompt, content }: { prompt: string; content: string }): Promise<string> { public async generateText({ prompt, content }: { prompt: string; content: string }): Promise<string> {

View File

@ -5,6 +5,7 @@ import { getMessageTitle } from '@renderer/services/MessagesService'
import store from '@renderer/store' import store from '@renderer/store'
import { setExportState } from '@renderer/store/runtime' import { setExportState } from '@renderer/store/runtime'
import { Message, Topic } from '@renderer/types' import { Message, Topic } from '@renderer/types'
import { removeSpecialCharactersForFileName } from '@renderer/utils/index'
export const messageToMarkdown = (message: Message) => { export const messageToMarkdown = (message: Message) => {
const roleText = message.role === 'user' ? '🧑‍💻 User' : '🤖 Assistant' const roleText = message.role === 'user' ? '🧑‍💻 User' : '🤖 Assistant'
@ -30,7 +31,7 @@ export const topicToMarkdown = async (topic: Topic) => {
} }
export const exportTopicAsMarkdown = async (topic: Topic) => { export const exportTopicAsMarkdown = async (topic: Topic) => {
const fileName = topic.name + '.md' const fileName = removeSpecialCharactersForFileName(topic.name) + '.md'
const markdown = await topicToMarkdown(topic) const markdown = await topicToMarkdown(topic)
window.api.file.save(fileName, markdown) window.api.file.save(fileName, markdown)
} }

View File

@ -180,6 +180,17 @@ export function removeSpecialCharacters(str: string) {
return str.replace(/[\n"]/g, '').replace(/[\p{M}\p{N}\p{P}\p{S}]/gu, '') return str.replace(/[\n"]/g, '').replace(/[\p{M}\p{N}\p{P}\p{S}]/gu, '')
} }
export function removeSpecialCharactersForTopicName(str: string) {
return str.replace(/[\r\n]+/g, ' ').trim()
}
export function removeSpecialCharactersForFileName(str: string) {
return str
.replace(/[<>:"/\\|?*.]/g, '_')
.replace(/[\r\n]+/g, ' ')
.trim()
}
export function generateColorFromChar(char: string) { export function generateColorFromChar(char: string) {
// 使用字符的Unicode值作为随机种子 // 使用字符的Unicode值作为随机种子
const seed = char.charCodeAt(0) const seed = char.charCodeAt(0)