Compare commits

..

No commits in common. "632caa28ac76f0841544ea3cd973b1a3369c50ca" and "622860aa0c5188f7310a2925abc351f814adfbaa" have entirely different histories.

11 changed files with 44 additions and 1800 deletions

1194
api.md

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lexiverse</title>
<title>Dictionay</title>
</head>
<body>
<div id="app"></div>

View File

@ -1,89 +0,0 @@
import apiClient from './client'
/**
* AI助手问答请求参数
*/
export interface AiQuestionRequest {
word: string
question: string
}
/**
* AI助手问答响应
*/
export interface AiQuestionResponse {
word: string
answer: string
model: string
tokens_used: number
}
/**
*
* @param data
* @returns AI回答
*/
export const askAiAssistant = async (data: AiQuestionRequest): Promise<AiQuestionResponse> => {
try {
const response = await apiClient.post<AiQuestionResponse>('/ai_assist/word/exp', data)
return response.data
} catch (error: any) {
console.error('AI Assistant API error:', error)
if (error.response) {
const status = error.response.status
const message = error.response.data?.message || 'AI助手请求失败'
switch (status) {
case 400:
throw new Error('本月API使用量已超')
case 401:
throw new Error('请先登录后再使用AI助手')
case 500:
throw new Error('AI服务暂时不可用请稍后重试')
default:
throw new Error(`AI助手请求失败${message}`)
}
} else if (error.request) {
throw new Error('网络连接失败,请检查网络连接')
} else {
throw new Error('AI助手请求失败请稍后重试')
}
}
}
/**
*
* @param word
* @returns
*/
export const clearAiHistory = async (word: string): Promise<{ msg: string }> => {
try {
const response = await apiClient.post<{ msg: string }>('/ai_assist/clear', null, {
params: { word }
})
return response.data
} catch (error: any) {
console.error('Clear AI history error:', error)
if (error.response) {
const message = error.response.data?.message || '清除聊天记录失败'
throw new Error(message)
} else {
throw new Error('清除聊天记录失败,请稍后重试')
}
}
}
/**
* AI对话
* @returns
*/
export const universalAiChat = async (): Promise<void> => {
try {
await apiClient.post('/ai_assist/univer')
} catch (error: any) {
console.error('Universal AI chat error:', error)
throw new Error('通用AI对话功能暂未开放')
}
}

View File

@ -8,11 +8,8 @@ export interface LoginRequest {
export interface RegisterRequest {
username: string
password: string
email: string
phone?: string
lang_pref: 'jp' | 'fr' | 'private'
portrait?: string
code: string
}
export interface LoginResponse {
@ -30,34 +27,6 @@ export interface RegisterResponse {
message: string
}
export interface EmailVerifyRequest {
email: string
}
export interface EmailVerifyResponse {
message: string
}
export interface VerifyCodeRequest {
email: string
code: string
}
export interface VerifyCodeResponse {
reset_token: string
}
export interface ResetPasswordRequest {
password: string
}
export interface UpdateUserRequest {
current_password: string
new_username?: string
new_password?: string
new_language?: 'jp' | 'fr' | 'private'
}
// 用户登录
export const login = async (credentials: LoginRequest): Promise<LoginResponse> => {
const response = await apiClient.post('/users/login', credentials)
@ -75,40 +44,6 @@ export const register = async (userData: RegisterRequest): Promise<RegisterRespo
return response.data
}
// 邮箱验证 - 注册时
export const sendEmailVerification = async (data: EmailVerifyRequest): Promise<EmailVerifyResponse> => {
const response = await apiClient.post('/users/register/email_verify', data)
return response.data
}
// 邮箱找回密码(发送验证码)
export const sendPasswordResetEmail = async (data: EmailVerifyRequest): Promise<EmailVerifyResponse> => {
const response = await apiClient.post('/users/auth/forget-password/email', data)
return response.data
}
// 邮箱验证码验证
export const verifyEmailCode = async (data: VerifyCodeRequest): Promise<VerifyCodeResponse> => {
const response = await apiClient.post('/users/auth/varify_code/email', data)
return response.data
}
// 重置密码
export const resetPassword = async (data: ResetPasswordRequest, resetToken: string): Promise<{ message: string }> => {
const response = await apiClient.post('/users/auth/reset-password', data, {
headers: {
'x-reset-token': resetToken
}
})
return response.data
}
// 更新用户信息
export const updateUser = async (data: UpdateUserRequest): Promise<{ message: string }> => {
const response = await apiClient.put('/users/update', data)
return response.data
}
// 用户登出
export const logout = async (): Promise<void> => {
await apiClient.post('/users/logout')

View File

@ -1,41 +0,0 @@
import apiClient from './client'
/**
*
*/
export interface WordCommentRequest {
comment_word: string
comment_content: string
}
/**
*
* @param lang (fr jp)
* @param data
* @returns
*/
export const addWordComment = async (lang: 'fr' | 'jp', data: WordCommentRequest): Promise<void> => {
try {
await apiClient.post(`/comment/word/${lang}`, data)
} catch (error: any) {
console.error('Add word comment error:', error)
if (error.response) {
const status = error.response.status
const message = error.response.data?.message || '添加评论失败'
switch (status) {
case 401:
throw new Error('请先登录后再添加评论')
case 422:
throw new Error('评论内容格式不正确')
default:
throw new Error(`添加评论失败:${message}`)
}
} else if (error.request) {
throw new Error('网络连接失败,请检查网络连接')
} else {
throw new Error('添加评论失败,请稍后重试')
}
}
}

View File

@ -12,17 +12,13 @@ export interface WordDefinition {
}
export interface SearchParams {
language: string
query: string
sort?: string
order?: string
lang_pref: string
query_word: string
}
export interface SearchSuggestParams {
query: string
language: string
sort?: string
order?: string
}
export interface SearchSuggestResponse {
@ -32,97 +28,21 @@ export interface SearchSuggestResponse {
// 搜索单词
export const searchWord = async (params: SearchParams): Promise<WordDefinition> => {
const packet = {
language: params.language,
query: params.query,
sort: params.sort || "relevance",
order: params.order || "des"
language: params.lang_pref,
query: params.query_word,
sort: "relevance",
order: "asc"
}
const response = await apiClient.post('/search/word', packet)
const response = await apiClient.post('/search', packet)
console.log(response)
return response.data
}
// 搜索推荐
export const searchSuggest = async (params: SearchSuggestParams): Promise<SearchSuggestResponse> => {
const response = await apiClient.post('/search/list/word', {
const response = await apiClient.post('/search/list', {
query: params.query,
language: params.language,
sort: params.sort || "relevance",
order: params.order || "des"
})
return response.data
}
/**
*
*/
export interface ProverbDetail {
text: string
chi_exp: string
freq: number
}
export interface ProverbListItem {
id: number
proverb: string
chi_exp: string
}
// 获取法语谚语详情
export const getProverbDetail = async (proverbId: number): Promise<{ result: ProverbDetail }> => {
const formData = new FormData()
formData.append('proverb_id', proverbId.toString())
const response = await apiClient.post('/search/proverb', formData, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
return response.data
}
// 获取谚语联想建议
export const searchProverbSuggest = async (query: string, dictLanguage: string): Promise<{ list: ProverbListItem[] }> => {
const response = await apiClient.post('/search/list/proverb', {
query,
dict_language: dictLanguage
})
return response.data
}
/**
*
*/
export interface IdiomDetail {
id: number
text: string
search_text: string
chi_exp: string
example: string
freq: number
}
export interface IdiomListItem {
id: number
proverb: string
chi_exp: string
}
// 获取日语惯用语详情
export const getIdiomDetail = async (queryId: number): Promise<{ result: IdiomDetail }> => {
const response = await apiClient.post('/search/idiom', null, {
params: { query_id: queryId }
})
return response.data
}
// 获取日语惯用语联想建议
export const searchIdiomSuggest = async (query: string, dictLanguage: string = 'jp'): Promise<{ list: IdiomListItem[] }> => {
const response = await apiClient.post('/search/list/idiom', {
query,
dict_language: dictLanguage
language: params.language
})
return response.data
}

View File

@ -1,61 +0,0 @@
import apiClient from './client'
/**
*
*/
export type FeedbackType =
| 'ui_design'
| 'dict_fr'
| 'dict_jp'
| 'user'
| 'translate'
| 'writting'
| 'ai_assist'
| 'pronounce'
/**
*
*/
export interface FeedbackRequest {
report_part: FeedbackType
text: string
}
/**
*
*/
export interface FeedbackResponse {
massages: string
}
/**
*
* @param data
* @returns
*/
export const submitFeedback = async (data: FeedbackRequest): Promise<FeedbackResponse> => {
try {
const response = await apiClient.post<FeedbackResponse>('/improvements', data)
return response.data
} catch (error: any) {
console.error('Submit feedback error:', error)
if (error.response) {
const status = error.response.status
const message = error.response.data?.message || '提交反馈失败'
switch (status) {
case 401:
throw new Error('请先登录后再提交反馈')
case 422:
throw new Error('反馈内容格式不正确,请检查后重试')
default:
throw new Error(`提交反馈失败:${message}`)
}
} else if (error.request) {
throw new Error('网络连接失败,请检查网络连接')
} else {
throw new Error('提交反馈失败,请稍后重试')
}
}
}

View File

@ -1,142 +0,0 @@
import apiClient from './client'
/**
*
*/
export interface ArticleReviewRequest {
title_content: string
article_type: string
}
/**
*
*/
export interface ArticleReviewResponse {
reply: string
tokens: number
conversation_length: number
}
/**
*
*/
export interface ArticleQuestionRequest {
query: string
}
/**
*
*/
export interface ArticleQuestionResponse {
reply: string
tokens: number
conversation_length: number
}
/**
*
*/
export interface ResetSessionResponse {
message: string
}
/**
*
* @param data
* @param lang fr-FR
* @returns AI批改结果
*/
export const reviewArticle = async (
data: ArticleReviewRequest,
lang: string = 'fr-FR'
): Promise<ArticleReviewResponse> => {
try {
const response = await apiClient.post<ArticleReviewResponse>(
'/article-director/article',
data,
{
params: { lang }
}
)
return response.data
} catch (error: any) {
console.error('Article review API error:', error)
if (error.response) {
const status = error.response.status
const message = error.response.data?.message || '作文批改失败'
switch (status) {
case 401:
throw new Error('请先登录后再使用作文批改功能')
case 500:
throw new Error('作文批改服务暂时不可用,请稍后重试')
default:
throw new Error(`作文批改失败:${message}`)
}
} else if (error.request) {
throw new Error('网络连接失败,请检查网络连接')
} else {
throw new Error('作文批改请求失败,请稍后重试')
}
}
}
/**
*
* @param data
* @returns AI回答
*/
export const askArticleQuestion = async (
data: ArticleQuestionRequest
): Promise<ArticleQuestionResponse> => {
try {
const response = await apiClient.post<ArticleQuestionResponse>(
'/article-director/question',
data
)
return response.data
} catch (error: any) {
console.error('Article question API error:', error)
if (error.response) {
const status = error.response.status
const message = error.response.data?.message || '追问失败'
switch (status) {
case 401:
throw new Error('请先登录后再使用追问功能')
case 500:
throw new Error('追问服务暂时不可用,请稍后重试')
default:
throw new Error(`追问失败:${message}`)
}
} else if (error.request) {
throw new Error('网络连接失败,请检查网络连接')
} else {
throw new Error('追问请求失败,请稍后重试')
}
}
}
/**
*
* @returns
*/
export const resetArticleSession = async (): Promise<ResetSessionResponse> => {
try {
const response = await apiClient.post<ResetSessionResponse>(
'/article-director/reset'
)
return response.data
} catch (error: any) {
console.error('Reset article session error:', error)
if (error.response) {
const message = error.response.data?.message || '重置会话失败'
throw new Error(message)
} else {
throw new Error('重置会话失败,请稍后重试')
}
}
}

View File

@ -90,8 +90,8 @@ const handleSearch = async (query: string, lang: string) => {
try {
const result = await searchWord({
language: lang,
query: query.trim()
lang_pref: lang,
query_word: query.trim()
})
searchResult.value = result
} catch (err: any) {

View File

@ -64,7 +64,7 @@
class="px-8 border border-blue-700 focus:border-blue-500 rounded-full outline-none w-full h-[63px] text-2xl"
/>
<!-- 注册时显示额外字段 -->
<!-- 注册时显示确认密码 -->
<template v-if="!isLogin">
<label class="block mt-8 mb-6 text-gray-500 text-xl">确认密码</label>
<input
@ -74,42 +74,6 @@
class="px-8 border border-blue-700 focus:border-blue-500 rounded-full outline-none w-full h-[63px] text-2xl"
/>
<label class="block mt-8 mb-6 text-gray-500 text-xl">邮箱</label>
<input
v-model="registerForm.email"
type="email"
required
placeholder="请输入邮箱地址"
class="px-8 border border-blue-700 focus:border-blue-500 rounded-full outline-none w-full h-[63px] text-2xl"
/>
<label class="block mt-8 mb-6 text-gray-500 text-xl">邮箱验证码</label>
<div class="flex gap-4">
<input
v-model="registerForm.code"
type="text"
required
placeholder="请输入邮箱验证码"
class="flex-1 px-8 border border-blue-700 focus:border-blue-500 rounded-full outline-none h-[63px] text-2xl"
/>
<button
type="button"
@click="sendVerificationCode"
:disabled="sendingCode || countdown > 0 || !registerForm.email"
class="bg-blue-700 hover:bg-blue-600 disabled:opacity-50 px-6 rounded-full text-white text-lg whitespace-nowrap disabled:cursor-not-allowed"
>
{{ countdown > 0 ? `${countdown}秒后重试` : (codeSent ? '重新发送' : '发送验证码') }}
</button>
</div>
<label class="block mt-8 mb-6 text-gray-500 text-xl">手机号可选</label>
<input
v-model="registerForm.phone"
type="tel"
placeholder="请输入手机号(可选)"
class="px-8 border border-blue-700 focus:border-blue-500 rounded-full outline-none w-full h-[63px] text-2xl"
/>
<label class="block mt-8 mb-6 text-gray-500 text-xl">语言偏好</label>
<select
v-model="registerForm.lang_pref"
@ -140,11 +104,10 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuth } from '../composables/useAuth'
import { useModal } from '../composables/useModal'
import { sendEmailVerification } from '../api/auth'
const router = useRouter()
const { login, register } = useAuth()
@ -160,17 +123,11 @@ const loginForm = ref({
const registerForm = ref({
username: '',
password: '',
email: '',
phone: '',
lang_pref: 'fr' as 'jp' | 'fr' | 'private',
portrait: '',
code: ''
portrait: ''
})
const confirmPassword = ref('')
const sendingCode = ref(false)
const codeSent = ref(false)
const countdown = ref(0)
const loading = ref(false)
const error = ref('')
const successMessage = ref('')
@ -268,15 +225,10 @@ const handleRegister = async () => {
registerForm.value = {
username: '',
password: '',
email: '',
phone: '',
lang_pref: 'fr',
portrait: '',
code: ''
portrait: ''
}
confirmPassword.value = ''
codeSent.value = false
countdown.value = 0
}, 2000)
} else {
//
@ -293,46 +245,6 @@ const handleRegister = async () => {
}
}
//
const sendVerificationCode = async () => {
if (!registerForm.value.email) {
error.value = '请先输入邮箱地址'
return
}
//
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(registerForm.value.email)) {
error.value = '请输入有效的邮箱地址'
return
}
sendingCode.value = true
error.value = ''
try {
await sendEmailVerification({ email: registerForm.value.email })
showSuccess('验证码已发送到您的邮箱,请查收')
codeSent.value = true
//
countdown.value = 60
const timer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timer)
}
}, 1000)
} catch (err: any) {
console.error('Send verification code error:', err)
const errorMessage = err.response?.data?.detail || err.response?.data?.message || '发送验证码失败'
showError(errorMessage)
error.value = errorMessage
} finally {
sendingCode.value = false
}
}
//
const clearMessages = () => {
error.value = ''
@ -340,6 +252,7 @@ const clearMessages = () => {
}
//
import { watch } from 'vue'
watch(isLogin, () => {
clearMessages()
})

View File

@ -69,7 +69,7 @@
<h3 class="mb-4 font-semibold text-gray-700">AI写作指导</h3>
<div class="h-[300px] overflow-y-auto">
<div v-if="loading" class="flex justify-center items-center h-full text-gray-500">
<div class="border-blue-700 border-b-2 rounded-full w-8 h-8 animate-spin"></div>
<div class="border-b-2 border-blue-700 rounded-full w-8 h-8 animate-spin"></div>
<span class="ml-2">AI正在分析您的作文...</span>
</div>
<div v-else-if="writingFeedback" class="space-y-4">
@ -124,10 +124,9 @@
</template>
<script setup lang="ts">
import { ref, onUnmounted } from 'vue'
import { ref } from 'vue'
import AppHeader from '../components/AppHeader.vue'
import AppFooter from '../components/AppFooter.vue'
import { reviewArticle, resetArticleSession } from '../api/writing'
const writingTypes = [
{ value: 'essay', label: '议论文' },
@ -190,38 +189,42 @@ const getWritingHelp = async () => {
error.value = ''
try {
// API
const result = await reviewArticle({
title_content: userText.value.trim(),
article_type: writingTypes.find(t => t.value === selectedType.value)?.label || '议论文'
}, 'fr-FR')
// AI
await new Promise(resolve => setTimeout(resolve, 2000))
// APIreply
// overall
// APIJSON使
// AI
writingFeedback.value = {
overall: result.reply,
tokens: `使用Token数: ${result.tokens}`,
conversation_length: `对话长度: ${result.conversation_length}`
overall: `您的作文整体结构清晰,主题明确。建议在以下方面进行改进:`,
grammar: [
'注意动词变位的准确性',
'检查形容词与名词的性数一致',
'确保时态使用的连贯性'
],
vocabulary: [
'可以使用更多高级词汇来丰富表达',
'避免重复使用相同的词汇',
'尝试使用同义词增加文章的多样性'
],
structure: [
'段落之间的过渡可以更加自然',
'可以增加更多的连接词',
'结论部分可以更加有力'
],
style: [
'保持正式的写作风格',
'句子长度可以适当变化',
'增加一些修辞手法会更好'
]
}
} catch (err: any) {
console.error('Writing help error:', err)
error.value = err.message || '获取写作指导失败,请稍后重试'
error.value = '获取写作指导失败,请稍后重试'
} finally {
loading.value = false
}
}
//
onUnmounted(async () => {
try {
await resetArticleSession()
} catch (err) {
console.error('Failed to reset article session:', err)
}
})
const useTemplate = (template: string) => {
if (userText.value && !confirm('使用模板将替换当前内容,确定继续吗?')) {
return