fix 1
This commit is contained in:
1
src-tauri/.gitignore
vendored
1
src-tauri/.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
|
/target*/
|
||||||
|
|
||||||
# Generated by Tauri
|
# Generated by Tauri
|
||||||
# will have schema files for capabilities auto-completion
|
# will have schema files for capabilities auto-completion
|
||||||
|
|||||||
@@ -32,12 +32,19 @@ struct Delta {
|
|||||||
content: Option<String>,
|
content: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
struct TranslationChunkEvent {
|
||||||
|
request_id: String,
|
||||||
|
chunk: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn translate(
|
async fn translate(
|
||||||
app: AppHandle,
|
app: AppHandle,
|
||||||
api_address: String,
|
api_address: String,
|
||||||
api_key: String,
|
api_key: String,
|
||||||
payload: TranslationPayload,
|
payload: TranslationPayload,
|
||||||
|
request_id: String,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
// Ensure URL doesn't have double slashes if api_address ends with /
|
// Ensure URL doesn't have double slashes if api_address ends with /
|
||||||
@@ -55,6 +62,12 @@ async fn translate(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let status = res.status();
|
||||||
|
if !status.is_success() {
|
||||||
|
let error_text = res.text().await.unwrap_or_else(|_| format!("HTTP {}", status));
|
||||||
|
return Err(format!("HTTP {}: {}", status, error_text));
|
||||||
|
}
|
||||||
|
|
||||||
if !payload.stream {
|
if !payload.stream {
|
||||||
let text = res.text().await.map_err(|e| e.to_string())?;
|
let text = res.text().await.map_err(|e| e.to_string())?;
|
||||||
return Ok(text);
|
return Ok(text);
|
||||||
@@ -78,7 +91,11 @@ async fn translate(
|
|||||||
if let Some(choice) = json.choices.get(0) {
|
if let Some(choice) = json.choices.get(0) {
|
||||||
if let Some(delta) = &choice.delta {
|
if let Some(delta) = &choice.delta {
|
||||||
if let Some(content) = &delta.content {
|
if let Some(content) = &delta.content {
|
||||||
app.emit("translation-chunk", content).map_err(|e| e.to_string())?;
|
let event = TranslationChunkEvent {
|
||||||
|
request_id: request_id.clone(),
|
||||||
|
chunk: content.clone(),
|
||||||
|
};
|
||||||
|
app.emit("translation-chunk", event).map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { useClipboard } from '../composables/useClipboard';
|
|||||||
|
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const { activeCopyId, copyWithFeedback } = useClipboard();
|
const { activeCopyId, copyWithFeedback } = useClipboard();
|
||||||
|
interface TranslationChunkEvent { request_id: string; chunk: string; }
|
||||||
|
|
||||||
// UI States
|
// UI States
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
@@ -101,6 +102,7 @@ const myInput = ref('');
|
|||||||
const partnerInput = ref('');
|
const partnerInput = ref('');
|
||||||
const isTranslating = ref(false);
|
const isTranslating = ref(false);
|
||||||
const currentStreamingMessageId = ref<string | null>(null);
|
const currentStreamingMessageId = ref<string | null>(null);
|
||||||
|
const activeStreamRequestId = ref<string | null>(null);
|
||||||
|
|
||||||
// Dropdowns
|
// Dropdowns
|
||||||
const myToneDropdownOpen = ref(false);
|
const myToneDropdownOpen = ref(false);
|
||||||
@@ -115,10 +117,14 @@ const handleCreateSession = () => {
|
|||||||
|
|
||||||
let unlisten: (() => void) | null = null;
|
let unlisten: (() => void) | null = null;
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
unlisten = await listen<string>('translation-chunk', (event) => {
|
unlisten = await listen<TranslationChunkEvent>('translation-chunk', (event) => {
|
||||||
if (activeSession.value && currentStreamingMessageId.value) {
|
if (
|
||||||
|
activeSession.value &&
|
||||||
|
currentStreamingMessageId.value &&
|
||||||
|
event.payload.request_id === activeStreamRequestId.value
|
||||||
|
) {
|
||||||
settings.updateChatMessage(activeSession.value.id, currentStreamingMessageId.value, {
|
settings.updateChatMessage(activeSession.value.id, currentStreamingMessageId.value, {
|
||||||
translated: (activeSession.value.messages.find(m => m.id === currentStreamingMessageId.value)?.translated || '') + event.payload
|
translated: (activeSession.value.messages.find(m => m.id === currentStreamingMessageId.value)?.translated || '') + event.payload.chunk
|
||||||
});
|
});
|
||||||
|
|
||||||
// 优化:只有当正在流式输出的消息是最后一条时,才自动滚动到底部
|
// 优化:只有当正在流式输出的消息是最后一条时,才自动滚动到底部
|
||||||
@@ -207,14 +213,17 @@ const translateMessage = async (sender: 'me' | 'partner', retranslateId?: string
|
|||||||
],
|
],
|
||||||
stream: settings.enableStreaming
|
stream: settings.enableStreaming
|
||||||
};
|
};
|
||||||
|
const requestId = crypto.randomUUID();
|
||||||
|
|
||||||
settings.addLog('request', { type: retranslateId ? 'conversation-retranslate' : 'conversation', ...requestBody }, generateCurl(settings.apiBaseUrl, settings.apiKey, requestBody));
|
settings.addLog('request', { type: retranslateId ? 'conversation-retranslate' : 'conversation', ...requestBody }, generateCurl(settings.apiBaseUrl, settings.apiKey, requestBody));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (settings.enableStreaming) activeStreamRequestId.value = requestId;
|
||||||
const response = await invoke<string>('translate', {
|
const response = await invoke<string>('translate', {
|
||||||
apiAddress: settings.apiBaseUrl,
|
apiAddress: settings.apiBaseUrl,
|
||||||
apiKey: settings.apiKey,
|
apiKey: settings.apiKey,
|
||||||
payload: requestBody
|
payload: requestBody,
|
||||||
|
requestId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!settings.enableStreaming) {
|
if (!settings.enableStreaming) {
|
||||||
@@ -232,6 +241,7 @@ const translateMessage = async (sender: 'me' | 'partner', retranslateId?: string
|
|||||||
} finally {
|
} finally {
|
||||||
isTranslating.value = false;
|
isTranslating.value = false;
|
||||||
currentStreamingMessageId.value = null;
|
currentStreamingMessageId.value = null;
|
||||||
|
activeStreamRequestId.value = null;
|
||||||
|
|
||||||
// 只有新消息才滚动到底部
|
// 只有新消息才滚动到底部
|
||||||
if (!retranslateId) {
|
if (!retranslateId) {
|
||||||
@@ -317,6 +327,7 @@ const evaluateMessage = async (messageId: string, force = false) => {
|
|||||||
],
|
],
|
||||||
stream: false
|
stream: false
|
||||||
};
|
};
|
||||||
|
const requestId = crypto.randomUUID();
|
||||||
|
|
||||||
settings.addLog('request', { type: 'conversation-eval', ...requestBody }, generateCurl(evalApiBaseUrl, evalApiKey, requestBody));
|
settings.addLog('request', { type: 'conversation-eval', ...requestBody }, generateCurl(evalApiBaseUrl, evalApiKey, requestBody));
|
||||||
|
|
||||||
@@ -324,7 +335,8 @@ const evaluateMessage = async (messageId: string, force = false) => {
|
|||||||
const response = await invoke<string>('translate', {
|
const response = await invoke<string>('translate', {
|
||||||
apiAddress: evalApiBaseUrl,
|
apiAddress: evalApiBaseUrl,
|
||||||
apiKey: evalApiKey,
|
apiKey: evalApiKey,
|
||||||
payload: requestBody
|
payload: requestBody,
|
||||||
|
requestId
|
||||||
});
|
});
|
||||||
const fullResponseJson = JSON.parse(response);
|
const fullResponseJson = JSON.parse(response);
|
||||||
settings.addLog('response', fullResponseJson);
|
settings.addLog('response', fullResponseJson);
|
||||||
@@ -358,6 +370,7 @@ const refineMessage = async (messageId: string) => {
|
|||||||
if (selectedSuggestions.length === 0) return;
|
if (selectedSuggestions.length === 0) return;
|
||||||
|
|
||||||
const suggestionsText = selectedSuggestions.map((s: any) => `- ${s.text}`).join('\n');
|
const suggestionsText = selectedSuggestions.map((s: any) => `- ${s.text}`).join('\n');
|
||||||
|
const currentTranslation = msg.translated;
|
||||||
|
|
||||||
isAuditModalOpen.value = false; // 关闭弹窗开始润色
|
isAuditModalOpen.value = false; // 关闭弹窗开始润色
|
||||||
settings.updateChatMessage(activeSession.value.id, messageId, { isRefining: true, translated: '' });
|
settings.updateChatMessage(activeSession.value.id, messageId, { isRefining: true, translated: '' });
|
||||||
@@ -415,18 +428,21 @@ const refineMessage = async (messageId: string) => {
|
|||||||
model: refineModelName,
|
model: refineModelName,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: "system", content: systemPrompt },
|
{ role: "system", content: systemPrompt },
|
||||||
{ role: "user", content: `[Source Text]\n${msg.original}\n\n[Current Translation]\n${msg.translated}\n\n[Suggestions]\n${suggestionsText}` }
|
{ role: "user", content: `[Source Text]\n${msg.original}\n\n[Current Translation]\n${currentTranslation}\n\n[Suggestions]\n${suggestionsText}` }
|
||||||
],
|
],
|
||||||
stream: settings.enableStreaming
|
stream: settings.enableStreaming
|
||||||
};
|
};
|
||||||
|
const requestId = crypto.randomUUID();
|
||||||
|
|
||||||
settings.addLog('request', { type: 'conversation-refine', ...requestBody }, generateCurl(refineApiBaseUrl, refineApiKey, requestBody));
|
settings.addLog('request', { type: 'conversation-refine', ...requestBody }, generateCurl(refineApiBaseUrl, refineApiKey, requestBody));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (settings.enableStreaming) activeStreamRequestId.value = requestId;
|
||||||
const response = await invoke<string>('translate', {
|
const response = await invoke<string>('translate', {
|
||||||
apiAddress: refineApiBaseUrl,
|
apiAddress: refineApiBaseUrl,
|
||||||
apiKey: refineApiKey,
|
apiKey: refineApiKey,
|
||||||
payload: requestBody
|
payload: requestBody,
|
||||||
|
requestId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!settings.enableStreaming) {
|
if (!settings.enableStreaming) {
|
||||||
@@ -442,6 +458,7 @@ const refineMessage = async (messageId: string) => {
|
|||||||
} finally {
|
} finally {
|
||||||
settings.updateChatMessage(activeSession.value.id, messageId, { isRefining: false, evaluation: undefined });
|
settings.updateChatMessage(activeSession.value.id, messageId, { isRefining: false, evaluation: undefined });
|
||||||
currentStreamingMessageId.value = null;
|
currentStreamingMessageId.value = null;
|
||||||
|
activeStreamRequestId.value = null;
|
||||||
|
|
||||||
// 只有当润色的是最后一条消息时才滚动到底部
|
// 只有当润色的是最后一条消息时才滚动到底部
|
||||||
const lastMsg = activeSession.value.messages[activeSession.value.messages.length - 1];
|
const lastMsg = activeSession.value.messages[activeSession.value.messages.length - 1];
|
||||||
|
|||||||
@@ -53,12 +53,14 @@ const currentHistoryId = ref<string | null>(null);
|
|||||||
|
|
||||||
interface Suggestion { id: number; text: string; importance: number; }
|
interface Suggestion { id: number; text: string; importance: number; }
|
||||||
interface EvaluationResult { score: number; analysis: string; suggestions?: Suggestion[]; }
|
interface EvaluationResult { score: number; analysis: string; suggestions?: Suggestion[]; }
|
||||||
|
interface TranslationChunkEvent { request_id: string; chunk: string; }
|
||||||
|
|
||||||
const evaluationResult = ref<EvaluationResult | null>(null);
|
const evaluationResult = ref<EvaluationResult | null>(null);
|
||||||
const isEvaluating = ref(false);
|
const isEvaluating = ref(false);
|
||||||
const isRefining = ref(false);
|
const isRefining = ref(false);
|
||||||
const selectedSuggestionIds = ref<number[]>([]);
|
const selectedSuggestionIds = ref<number[]>([]);
|
||||||
const appliedSuggestionIds = ref<number[]>([]);
|
const appliedSuggestionIds = ref<number[]>([]);
|
||||||
|
const activeStreamRequestId = ref<string | null>(null);
|
||||||
|
|
||||||
const toggleSuggestion = (id: number) => {
|
const toggleSuggestion = (id: number) => {
|
||||||
if (!selectedSuggestionIds.value) selectedSuggestionIds.value = [];
|
if (!selectedSuggestionIds.value) selectedSuggestionIds.value = [];
|
||||||
@@ -69,9 +71,9 @@ const toggleSuggestion = (id: number) => {
|
|||||||
|
|
||||||
let unlisten: (() => void) | null = null;
|
let unlisten: (() => void) | null = null;
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
unlisten = await listen<string>('translation-chunk', (event) => {
|
unlisten = await listen<TranslationChunkEvent>('translation-chunk', (event) => {
|
||||||
if (isTranslating.value || isRefining.value) {
|
if ((isTranslating.value || isRefining.value) && event.payload.request_id === activeStreamRequestId.value) {
|
||||||
targetText.value += event.payload;
|
targetText.value += event.payload.chunk;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -148,11 +150,12 @@ const evaluateTranslation = async () => {
|
|||||||
messages: [ { role: "system", content: evaluationSystemPrompt }, { role: "user", content: evaluationUserPrompt } ],
|
messages: [ { role: "system", content: evaluationSystemPrompt }, { role: "user", content: evaluationUserPrompt } ],
|
||||||
stream: false
|
stream: false
|
||||||
};
|
};
|
||||||
|
const requestId = crypto.randomUUID();
|
||||||
|
|
||||||
settings.addLog('request', { type: 'evaluation', ...requestBody }, generateCurl(apiBaseUrl, apiKey, requestBody));
|
settings.addLog('request', { type: 'evaluation', ...requestBody }, generateCurl(apiBaseUrl, apiKey, requestBody));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await invoke<string>('translate', { apiAddress: apiBaseUrl, apiKey: apiKey, payload: requestBody });
|
const response = await invoke<string>('translate', { apiAddress: apiBaseUrl, apiKey: apiKey, payload: requestBody, requestId });
|
||||||
try {
|
try {
|
||||||
// 解析 API 的原始响应 JSON
|
// 解析 API 的原始响应 JSON
|
||||||
const fullResponseJson = JSON.parse(response);
|
const fullResponseJson = JSON.parse(response);
|
||||||
@@ -209,11 +212,13 @@ const refineTranslation = async () => {
|
|||||||
messages: [ { role: "system", content: refinementSystemPrompt }, { role: "user", content: refinementUserPrompt } ],
|
messages: [ { role: "system", content: refinementSystemPrompt }, { role: "user", content: refinementUserPrompt } ],
|
||||||
stream: settings.enableStreaming
|
stream: settings.enableStreaming
|
||||||
};
|
};
|
||||||
|
const requestId = crypto.randomUUID();
|
||||||
|
|
||||||
settings.addLog('request', { type: 'refinement', ...requestBody }, generateCurl(apiBaseUrl, apiKey, requestBody));
|
settings.addLog('request', { type: 'refinement', ...requestBody }, generateCurl(apiBaseUrl, apiKey, requestBody));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await invoke<string>('translate', { apiAddress: apiBaseUrl, apiKey: apiKey, payload: requestBody });
|
if (settings.enableStreaming) activeStreamRequestId.value = requestId;
|
||||||
|
const response = await invoke<string>('translate', { apiAddress: apiBaseUrl, apiKey: apiKey, payload: requestBody, requestId });
|
||||||
|
|
||||||
if (settings.enableStreaming) {
|
if (settings.enableStreaming) {
|
||||||
settings.addLog('response', targetText.value);
|
settings.addLog('response', targetText.value);
|
||||||
@@ -239,6 +244,7 @@ const refineTranslation = async () => {
|
|||||||
targetText.value = `Error: ${errorMsg}`;
|
targetText.value = `Error: ${errorMsg}`;
|
||||||
} finally {
|
} finally {
|
||||||
isRefining.value = false;
|
isRefining.value = false;
|
||||||
|
activeStreamRequestId.value = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,11 +273,13 @@ const translate = async () => {
|
|||||||
messages: [ { role: "system", content: systemMessage }, { role: "user", content: userMessage } ],
|
messages: [ { role: "system", content: systemMessage }, { role: "user", content: userMessage } ],
|
||||||
stream: settings.enableStreaming
|
stream: settings.enableStreaming
|
||||||
};
|
};
|
||||||
|
const requestId = crypto.randomUUID();
|
||||||
|
|
||||||
settings.addLog('request', requestBody, generateCurl(settings.apiBaseUrl, settings.apiKey, requestBody));
|
settings.addLog('request', requestBody, generateCurl(settings.apiBaseUrl, settings.apiKey, requestBody));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await invoke<string>('translate', { apiAddress: settings.apiBaseUrl, apiKey: settings.apiKey, payload: requestBody });
|
if (settings.enableStreaming) activeStreamRequestId.value = requestId;
|
||||||
|
const response = await invoke<string>('translate', { apiAddress: settings.apiBaseUrl, apiKey: settings.apiKey, payload: requestBody, requestId });
|
||||||
|
|
||||||
let finalTargetText = '';
|
let finalTargetText = '';
|
||||||
if (settings.enableStreaming) {
|
if (settings.enableStreaming) {
|
||||||
@@ -300,6 +308,7 @@ const translate = async () => {
|
|||||||
targetText.value = `Error: ${errorMsg}`;
|
targetText.value = `Error: ${errorMsg}`;
|
||||||
} finally {
|
} finally {
|
||||||
isTranslating.value = false;
|
isTranslating.value = false;
|
||||||
|
activeStreamRequestId.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.enableEvaluation) await evaluateTranslation();
|
if (settings.enableEvaluation) await evaluateTranslation();
|
||||||
@@ -656,4 +665,4 @@ const translate = async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user