mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-28 20:02:44 +08:00
调整AI助手样式为边栏
This commit is contained in:
parent
4f5ef89558
commit
196d54a993
@ -18,7 +18,7 @@
|
||||
|
||||
<!-- 命令设置栏 -->
|
||||
<CommandConfig
|
||||
v-if="!isRunCodePage"
|
||||
v-if="!isRunCodePage && showCommandConfig"
|
||||
v-model="commandManager.state.currentCommand"
|
||||
from="quickcommand"
|
||||
:expand-on-focus="true"
|
||||
@ -32,6 +32,8 @@
|
||||
commandManager.state.currentCommand.cursorPosition
|
||||
"
|
||||
:language="getLanguage()"
|
||||
@saveHistory="saveToHistory"
|
||||
@request-full-screen="requestFullScreen"
|
||||
placeholder="请输入代码"
|
||||
class="codeEditor"
|
||||
ref="editor"
|
||||
@ -95,6 +97,7 @@ export default {
|
||||
return {
|
||||
programLanguages: Object.keys(programs),
|
||||
showComposer: false,
|
||||
showCommandConfig: true,
|
||||
listener: null,
|
||||
composerInfo: {
|
||||
program: "quickcomposer",
|
||||
@ -236,6 +239,9 @@ export default {
|
||||
break;
|
||||
}
|
||||
},
|
||||
requestFullScreen(value) {
|
||||
this.showCommandConfig = !value;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -1,404 +0,0 @@
|
||||
<template>
|
||||
<q-card class="ai-dialog">
|
||||
<div class="header q-px-md q-py-sm">
|
||||
<q-icon name="smart_toy" size="24px" />
|
||||
<div class="text-h6">AI 助手</div>
|
||||
<AISelector v-model="selectedApi" />
|
||||
<q-space />
|
||||
<q-btn icon="close" flat round dense v-close-popup size="md" />
|
||||
</div>
|
||||
|
||||
<!-- 聊天记录区域 -->
|
||||
<q-scroll-area
|
||||
ref="scrollArea"
|
||||
class="chat-container"
|
||||
:vertical-thumb-style="{
|
||||
width: '5px',
|
||||
}"
|
||||
>
|
||||
<div class="chat-history q-px-md">
|
||||
<div
|
||||
v-for="(message, index) in chatHistory"
|
||||
:key="index"
|
||||
class="chat-message-wrapper"
|
||||
>
|
||||
<div :class="['chat-message', message.role]">
|
||||
<div class="avatar">
|
||||
<q-avatar size="28px">
|
||||
<q-icon
|
||||
:name="message.role === 'user' ? 'person' : 'smart_toy'"
|
||||
:color="message.role === 'user' ? 'white' : 'primary'"
|
||||
size="20px"
|
||||
/>
|
||||
</q-avatar>
|
||||
</div>
|
||||
<div class="message-bubble">
|
||||
<div
|
||||
v-if="message.role === 'assistant'"
|
||||
class="message-content markdown"
|
||||
v-html="getTrimContent(message.content)"
|
||||
/>
|
||||
<div v-else class="message-content" v-text="message.content" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="input-container q-px-md q-py-sm">
|
||||
<q-input
|
||||
v-model="prompt"
|
||||
type="textarea"
|
||||
filled
|
||||
dense
|
||||
autogrow
|
||||
autofocus
|
||||
:max-rows="3"
|
||||
placeholder="请描述你的需求,Enter 发送,Shift+Enter 换行"
|
||||
@keydown.enter.exact.prevent="handleSubmit"
|
||||
@keydown.shift.enter.prevent="prompt += '\n'"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<div class="row items-center q-gutter-x-md">
|
||||
<q-btn
|
||||
flat
|
||||
icon="delete_sweep"
|
||||
size="sm"
|
||||
dense
|
||||
:disable="chatHistory.length === 0"
|
||||
@click="clearHistory"
|
||||
>
|
||||
<q-tooltip>清空对话</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
@click="autoUpdateCode = !autoUpdateCode"
|
||||
:color="autoUpdateCode ? 'primary' : 'grey'"
|
||||
icon="auto_fix_high"
|
||||
size="sm"
|
||||
dense
|
||||
flat
|
||||
>
|
||||
<q-tooltip>
|
||||
{{
|
||||
autoUpdateCode
|
||||
? "自动更新代码(已开启)"
|
||||
: "自动更新代码(已关闭)"
|
||||
}}
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
:color="streamingResponse ? 'negative' : 'primary'"
|
||||
:icon="streamingResponse ? 'stop' : 'send'"
|
||||
size="sm"
|
||||
dense
|
||||
flat
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import AISelector from "components/ai/AISelector.vue";
|
||||
import { marked } from "marked";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
const quickcommandApi =
|
||||
require(`!raw-loader!plugins/monaco/types/quickcommand.api.d.ts`)
|
||||
.default.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||
.replace(/\n/g, "");
|
||||
const uToolsApi = require(`!raw-loader!plugins/monaco/types/utools.api.d.ts`)
|
||||
.default.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||
.replace(/\n/g, "");
|
||||
|
||||
export default defineComponent({
|
||||
name: "AIAssistantDialog",
|
||||
components: {
|
||||
AISelector,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prompt: "",
|
||||
selectedApi: {},
|
||||
streamingResponse: false,
|
||||
chatHistory: [],
|
||||
currentRequest: null,
|
||||
autoUpdateCode: true,
|
||||
scrollToBottomDebounce: null,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
emits: ["update-code"],
|
||||
methods: {
|
||||
scrollToBottom() {
|
||||
// 清除之前的定时器
|
||||
if (this.scrollToBottomDebounce) {
|
||||
clearTimeout(this.scrollToBottomDebounce);
|
||||
}
|
||||
|
||||
// 设置新的定时器,延迟执行滚动
|
||||
this.scrollToBottomDebounce = setTimeout(() => {
|
||||
const scrollArea = this.$refs.scrollArea;
|
||||
if (scrollArea) {
|
||||
const scrollTarget = scrollArea.getScrollTarget();
|
||||
scrollArea.setScrollPosition(
|
||||
"vertical",
|
||||
scrollTarget.scrollHeight,
|
||||
300
|
||||
);
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
async handleSubmit() {
|
||||
if (this.streamingResponse) {
|
||||
this.stopStreaming();
|
||||
return;
|
||||
}
|
||||
|
||||
const promptText = this.prompt.trim();
|
||||
if (!promptText || !this.selectedApi) return;
|
||||
|
||||
// 添加用户消息到历史记录
|
||||
this.chatHistory.push(
|
||||
{
|
||||
role: "user",
|
||||
content: promptText,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "",
|
||||
}
|
||||
);
|
||||
|
||||
// 添加消息后立即滚动到底部
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
|
||||
this.streamingResponse = true;
|
||||
this.prompt = ""; // 清空输入框
|
||||
|
||||
try {
|
||||
const response = await window.quickcommand.askAI(
|
||||
{
|
||||
prompt: promptText,
|
||||
role: this.getRolePrompt(this.language),
|
||||
context: this.chatHistory.slice(0, -2),
|
||||
},
|
||||
this.selectedApi,
|
||||
{
|
||||
showLoadingBar: false,
|
||||
stream: true,
|
||||
onStream: (text, controller, done) => {
|
||||
this.currentRequest = controller;
|
||||
if (text) {
|
||||
this.chatHistory[this.chatHistory.length - 1].content += text;
|
||||
this.$nextTick(() => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
}
|
||||
|
||||
if (done) {
|
||||
this.streamingResponse = false;
|
||||
if (this.autoUpdateCode) {
|
||||
const response =
|
||||
this.chatHistory[this.chatHistory.length - 1].content;
|
||||
const code = response.match(
|
||||
/```[a-z]*\n([\s\S]*?)\n```/
|
||||
)?.[1];
|
||||
|
||||
if (!code) return;
|
||||
|
||||
this.$emit("update-code", code);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success && !response.cancelled) {
|
||||
window.quickcommand.showMessageBox(response.error, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
window.quickcommand.showMessageBox(error.message, "error");
|
||||
this.streamingResponse = false;
|
||||
}
|
||||
},
|
||||
stopStreaming() {
|
||||
this.streamingResponse = false;
|
||||
if (this.currentRequest) {
|
||||
this.currentRequest.abort();
|
||||
this.currentRequest = null;
|
||||
}
|
||||
},
|
||||
clearHistory() {
|
||||
this.chatHistory = [];
|
||||
},
|
||||
getTrimContent(content) {
|
||||
const markedContent = marked(content.trim());
|
||||
// 解决think标签被错误地包裹在<p>标签中
|
||||
const processedContent = markedContent
|
||||
.replace("<p><think>", "<think><p>")
|
||||
.replace("</think></p>", "</p></think>")
|
||||
// 去除空的think标签
|
||||
.replace("<think>\n\n</think>", "");
|
||||
const purifiedContent = DOMPurify.sanitize(processedContent, {
|
||||
ADD_TAGS: ["think"],
|
||||
});
|
||||
return purifiedContent;
|
||||
},
|
||||
getRolePrompt(language) {
|
||||
const languageMap = {
|
||||
quickcommand: "NodeJS",
|
||||
javascript: "NodeJS",
|
||||
};
|
||||
const commonInstructions = `请作为一名专业的开发专家,根据我的需求编写${languageMap[language]}代码,并请遵循以下原则:
|
||||
- 编写简洁、可读性强的代码
|
||||
- 遵循${language}最佳实践和设计模式
|
||||
- 使用恰当的命名规范和代码组织
|
||||
- 添加必要的错误处理和边界检查
|
||||
- 保持中文注释的准确性和专业性
|
||||
- 提供必要的使用说明
|
||||
`;
|
||||
|
||||
// 针对不同语言的特殊提示
|
||||
let languageSpecific = {
|
||||
javascript: `- 优先使用现代ES6+特性
|
||||
- 使用NodeJS原生API和模块`,
|
||||
python: `- 遵循PEP8规范`,
|
||||
};
|
||||
languageSpecific.quickcommand = `${languageSpecific.javascript}
|
||||
- 支持使用以下uTools接口: ${uToolsApi}
|
||||
- 支持使用以下quickcommand接口: ${quickcommandApi}`;
|
||||
|
||||
// 获取语言特定的提示,如果没有则使用空字符串
|
||||
const specificInstructions =
|
||||
languageSpecific[language.toLowerCase()] || "";
|
||||
|
||||
const lastInstructions =
|
||||
"\n请直接生成代码,任何情况下都不需要做解释和说明";
|
||||
|
||||
return commonInstructions + specificInstructions + lastInstructions;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-dialog {
|
||||
width: 800px;
|
||||
max-width: 90vw;
|
||||
height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.chat-message-wrapper {
|
||||
margin-bottom: 1rem;
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
.chat-message.user {
|
||||
margin-left: auto;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.chat-message .avatar {
|
||||
background: var(--q-primary);
|
||||
border-radius: 50%;
|
||||
padding: 2px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chat-message.assistant .avatar {
|
||||
background: var(--transparent-bg-color);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chat-message.user .avatar {
|
||||
background: var(--q-primary);
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 8px 12px;
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-content :deep(think) {
|
||||
color: #8b8b8b;
|
||||
display: block;
|
||||
border-left: 4px solid #8b8b8b;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chat-message.user .message-bubble {
|
||||
background-color: var(--q-primary);
|
||||
color: white;
|
||||
border-top-right-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.chat-message.assistant .message-bubble {
|
||||
background-color: var(--transparent-bg-color);
|
||||
border-top-left-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.message-content {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗色模式适配 */
|
||||
.body--dark .chat-message.assistant .message-bubble {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
</style>
|
312
src/components/ai/AIAssistantSideBar.vue
Normal file
312
src/components/ai/AIAssistantSideBar.vue
Normal file
@ -0,0 +1,312 @@
|
||||
<template>
|
||||
<q-card class="ai-assistant">
|
||||
<div class="row items-center justify-between q-pa-xs no-wrap">
|
||||
<div class="row items-center q-gutter-x-xs no-wrap">
|
||||
<q-icon name="smart_toy" size="20px" />
|
||||
<div class="text-subtitle1">AI 助手</div>
|
||||
<AISelector v-model="selectedApi" :dense="true" />
|
||||
</div>
|
||||
<q-btn icon="close" size="sm" flat dense @click.stop="$emit('close')" />
|
||||
</div>
|
||||
|
||||
<!-- 聊天记录区域 -->
|
||||
<AIChatHistory
|
||||
:messages="chatHistory"
|
||||
@update-code="$emit('update-code', ...$event)"
|
||||
/>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="input-container q-px-sm q-py-xs">
|
||||
<div class="prompt-input">
|
||||
<q-input
|
||||
v-model="prompt"
|
||||
type="textarea"
|
||||
borderless
|
||||
dense
|
||||
autogrow
|
||||
autofocus
|
||||
placeholder="请描述需求,Enter 发送,Shift+Enter 换行"
|
||||
@keydown.enter.exact.prevent="handleSubmit"
|
||||
@keydown.shift.enter.prevent="prompt += '\n'"
|
||||
/>
|
||||
</div>
|
||||
<div class="row items-center justify-between q-gutter-x-xs">
|
||||
<div class="row items-center q-gutter-x-xs">
|
||||
<q-btn flat dense size="sm" icon="delete_sweep" @click="clearHistory">
|
||||
<q-tooltip>清空对话</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="sm"
|
||||
icon="code"
|
||||
:color="sendCode ? 'primary' : 'grey'"
|
||||
@click="sendCode = !sendCode"
|
||||
>
|
||||
<q-tooltip>
|
||||
{{
|
||||
sendCode
|
||||
? "将当前代码提交给AI(已开启)"
|
||||
: "将当前代码提交给AI(已关闭)"
|
||||
}}
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="sm"
|
||||
icon="auto_fix_high"
|
||||
:color="autoUpdateCode ? 'primary' : 'grey'"
|
||||
@click="toggleAutoUpdate"
|
||||
>
|
||||
<q-tooltip>
|
||||
{{
|
||||
autoUpdateCode ? "自动更新代码(已开启)" : "自动更新代码(已关闭)"
|
||||
}}
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="sm"
|
||||
:color="streamingResponse ? 'negative' : 'primary'"
|
||||
:icon="streamingResponse ? 'stop' : 'send'"
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import AISelector from "components/ai/AISelector.vue";
|
||||
import AIChatHistory from "components/ai/AIChatHistory.vue";
|
||||
import { marked } from "marked";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
const quickcommandApi =
|
||||
require(`!raw-loader!plugins/monaco/types/quickcommand.api.d.ts`)
|
||||
.default.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||
.replace(/\n/g, "");
|
||||
const uToolsApi = require(`!raw-loader!plugins/monaco/types/utools.api.d.ts`)
|
||||
.default.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||
.replace(/\n/g, "");
|
||||
|
||||
export default defineComponent({
|
||||
name: "AIAssistantSideBar",
|
||||
components: {
|
||||
AISelector,
|
||||
AIChatHistory,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prompt: "",
|
||||
selectedApi: {},
|
||||
streamingResponse: false,
|
||||
chatHistory: [],
|
||||
currentRequest: null,
|
||||
autoUpdateCode: localStorage.getItem("ai_auto_update") !== "false",
|
||||
sendCode: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
emits: ["update-code", "close"],
|
||||
methods: {
|
||||
async handleSubmit() {
|
||||
const sendCode = this.sendCode;
|
||||
this.sendCode = false;
|
||||
|
||||
if (this.streamingResponse) {
|
||||
this.stopStreaming();
|
||||
return;
|
||||
}
|
||||
|
||||
const code = this.code
|
||||
? `这是当前代码:\n-----代码开始-----\n${this.code}\n-----代码结束-----\n`
|
||||
: "";
|
||||
|
||||
const promptText = sendCode
|
||||
? code + this.prompt.trim()
|
||||
: this.prompt.trim();
|
||||
|
||||
if (!promptText || !this.selectedApi) return;
|
||||
|
||||
this.chatHistory.push(
|
||||
{
|
||||
role: "user",
|
||||
content: promptText,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "",
|
||||
}
|
||||
);
|
||||
|
||||
this.streamingResponse = true;
|
||||
this.prompt = "";
|
||||
|
||||
try {
|
||||
const response = await window.quickcommand.askAI(
|
||||
{
|
||||
prompt: promptText,
|
||||
role: this.getRolePrompt(this.language),
|
||||
context: this.chatHistory.slice(0, -2),
|
||||
},
|
||||
this.selectedApi,
|
||||
{
|
||||
showLoadingBar: false,
|
||||
stream: true,
|
||||
onStream: (text, controller, done) => {
|
||||
this.currentRequest = controller;
|
||||
if (text) {
|
||||
this.chatHistory[this.chatHistory.length - 1].content += text;
|
||||
}
|
||||
|
||||
if (done) {
|
||||
this.streamingResponse = false;
|
||||
if (this.autoUpdateCode) {
|
||||
const response =
|
||||
this.chatHistory[this.chatHistory.length - 1].content;
|
||||
const code = response.match(
|
||||
/```[a-z]*\n([\s\S]*?)\n```/
|
||||
)?.[1];
|
||||
|
||||
if (!code) return;
|
||||
|
||||
this.$emit("update-code", "replace", code);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success && !response.cancelled) {
|
||||
window.quickcommand.showMessageBox(response.error, "error");
|
||||
}
|
||||
} catch (error) {
|
||||
window.quickcommand.showMessageBox(error.message, "error");
|
||||
this.streamingResponse = false;
|
||||
}
|
||||
},
|
||||
stopStreaming() {
|
||||
this.streamingResponse = false;
|
||||
if (this.currentRequest) {
|
||||
this.currentRequest.abort();
|
||||
this.currentRequest = null;
|
||||
}
|
||||
},
|
||||
clearHistory() {
|
||||
if (this.chatHistory.length === 0) return;
|
||||
this.chatHistory = [];
|
||||
localStorage.removeItem("ai_chat_history");
|
||||
},
|
||||
saveChatHistory() {
|
||||
localStorage.setItem("ai_chat_history", JSON.stringify(this.chatHistory));
|
||||
},
|
||||
loadChatHistory() {
|
||||
const history = localStorage.getItem("ai_chat_history");
|
||||
if (history) {
|
||||
try {
|
||||
this.chatHistory = JSON.parse(history);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse chat history:", e);
|
||||
this.chatHistory = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
toggleAutoUpdate() {
|
||||
this.autoUpdateCode = !this.autoUpdateCode;
|
||||
localStorage.setItem("ai_auto_update", this.autoUpdateCode);
|
||||
},
|
||||
getTrimContent(content) {
|
||||
const markedContent = marked(content.trim());
|
||||
// 解决think标签被错误地包裹在<p>标签中
|
||||
const processedContent = markedContent
|
||||
.replace("<p><think>", "<think><p>")
|
||||
.replace("</think></p>", "</p></think>")
|
||||
// 去除空的think标签
|
||||
.replace("<think>\n\n</think>", "");
|
||||
const purifiedContent = DOMPurify.sanitize(processedContent, {
|
||||
ADD_TAGS: ["think"],
|
||||
});
|
||||
return purifiedContent;
|
||||
},
|
||||
getRolePrompt(language) {
|
||||
const languageMap = {
|
||||
quickcommand: "NodeJS",
|
||||
javascript: "NodeJS",
|
||||
cmd: "bat",
|
||||
};
|
||||
const commonInstructions = `请作为一名专业的开发专家,根据我的需求编写${languageMap[language]}代码,并请遵循以下原则:
|
||||
- 编写简洁、可读性强的代码
|
||||
- 遵循${language}最佳实践和设计模式
|
||||
- 使用恰当的命名规范和代码组织
|
||||
- 添加必要的错误处理和边界检查
|
||||
- 保持中文注释的准确性和专业性
|
||||
- 提供必要的使用说明
|
||||
`;
|
||||
|
||||
// 针对不同语言的特殊提示
|
||||
let languageSpecific = {
|
||||
javascript: `- 优先使用现代ES6+特性
|
||||
- 使用NodeJS原生API和模块`,
|
||||
python: `- 遵循PEP8规范`,
|
||||
};
|
||||
languageSpecific.quickcommand = `${languageSpecific.javascript}
|
||||
- 支持使用以下uTools接口: ${uToolsApi}
|
||||
- 支持使用以下quickcommand接口: ${quickcommandApi}`;
|
||||
|
||||
// 获取语言特定的提示,如果没有则使用空字符串
|
||||
const specificInstructions =
|
||||
languageSpecific[language.toLowerCase()] || "";
|
||||
|
||||
const lastInstructions =
|
||||
"\n请直接生成代码,任何情况下都不需要做解释和说明";
|
||||
|
||||
return commonInstructions + specificInstructions + lastInstructions;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(this.loadChatHistory);
|
||||
},
|
||||
beforeUnmount() {
|
||||
setTimeout(this.saveChatHistory);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-assistant {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
flex-shrink: 0;
|
||||
background: var(--utools-bg-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.prompt-input {
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.prompt-input :deep(textarea) {
|
||||
font-size: 13px;
|
||||
padding: 2px 4px !important;
|
||||
}
|
||||
</style>
|
281
src/components/ai/AIChatHistory.vue
Normal file
281
src/components/ai/AIChatHistory.vue
Normal file
@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<q-scroll-area
|
||||
ref="scrollArea"
|
||||
class="chat-container"
|
||||
:vertical-thumb-style="{
|
||||
width: '5px',
|
||||
}"
|
||||
:horizontal-thumb-style="{
|
||||
height: '5px',
|
||||
}"
|
||||
>
|
||||
<div class="chat-history">
|
||||
<div
|
||||
v-for="(message, index) in messages"
|
||||
:key="index"
|
||||
class="chat-message-wrapper"
|
||||
>
|
||||
<div :class="['chat-message', message.role]">
|
||||
<div class="message-bubble">
|
||||
<div
|
||||
v-if="message.role === 'assistant'"
|
||||
class="message-content markdown"
|
||||
v-html="getAssistantMsg(message.content)"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="message-content"
|
||||
v-text="getUserMsg(message.content)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-scroll-area>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from "vue";
|
||||
import { marked } from "marked";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
export default defineComponent({
|
||||
name: "AIChatHistory",
|
||||
props: {
|
||||
messages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ["update-code"],
|
||||
data() {
|
||||
return {
|
||||
timeoutId: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
debounce(fn) {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
this.timeoutId = setTimeout(fn, 500);
|
||||
},
|
||||
|
||||
scrollToBottom() {
|
||||
const scrollArea = this.$refs.scrollArea;
|
||||
if (scrollArea) {
|
||||
const scrollTarget = scrollArea.getScrollTarget();
|
||||
scrollArea.setScrollPosition(
|
||||
"vertical",
|
||||
scrollTarget.scrollHeight,
|
||||
300
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
getAssistantMsg(content) {
|
||||
const markedContent = marked(content.trim());
|
||||
const processedContent = markedContent
|
||||
.replace("<p><think>", "<think><p>")
|
||||
.replace("</think></p>", "</p></think>")
|
||||
.replace("<think>\n\n</think>", "");
|
||||
const purifiedContent = DOMPurify.sanitize(processedContent, {
|
||||
ADD_TAGS: ["think"],
|
||||
});
|
||||
return purifiedContent;
|
||||
},
|
||||
|
||||
getUserMsg(content) {
|
||||
content = content.replace(
|
||||
/这是当前代码:\n-----代码开始-----([\s\S]*?)-----代码结束-----\n/g,
|
||||
"这是当前代码:(代码已隐藏)\n"
|
||||
);
|
||||
return content;
|
||||
},
|
||||
|
||||
setupMessageActions() {
|
||||
const codeBlocks = document.querySelectorAll(".message-content pre");
|
||||
codeBlocks.forEach((codeBlock) => {
|
||||
if (codeBlock.querySelector(".code-actions")) return;
|
||||
|
||||
const actionGroup = document.createElement("div");
|
||||
actionGroup.className = "code-actions";
|
||||
|
||||
const copyBtn = document.createElement("button");
|
||||
copyBtn.innerHTML = '<i class="material-icons">content_copy</i>';
|
||||
copyBtn.title = "复制代码";
|
||||
copyBtn.onclick = () => {
|
||||
navigator.clipboard.writeText(codeBlock.textContent);
|
||||
window.quickcommand.showNotification("代码已复制到剪贴板");
|
||||
};
|
||||
|
||||
const insertBtn = document.createElement("button");
|
||||
insertBtn.innerHTML = '<i class="material-icons">read_more</i>';
|
||||
insertBtn.title = "插入到光标位置";
|
||||
insertBtn.onclick = () => {
|
||||
this.$emit("update-code", "insert", codeBlock.textContent);
|
||||
};
|
||||
|
||||
const applyBtn = document.createElement("button");
|
||||
applyBtn.innerHTML = '<i class="material-icons">done_all</i>';
|
||||
applyBtn.title = "替换当前代码";
|
||||
applyBtn.onclick = () => {
|
||||
this.$emit("update-code", "replace", codeBlock.textContent);
|
||||
};
|
||||
|
||||
actionGroup.appendChild(copyBtn);
|
||||
actionGroup.appendChild(insertBtn);
|
||||
actionGroup.appendChild(applyBtn);
|
||||
codeBlock.appendChild(actionGroup);
|
||||
});
|
||||
|
||||
const links = document.querySelectorAll(".message-content a");
|
||||
links.forEach((link) => {
|
||||
if (link.hasAttribute("data-processed")) return;
|
||||
|
||||
const href = link.getAttribute("href");
|
||||
if (href) {
|
||||
link.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
window.utools.shellOpenExternal(href);
|
||||
};
|
||||
link.setAttribute("data-processed", "true");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateView() {
|
||||
this.scrollToBottom();
|
||||
this.debounce(this.setupMessageActions);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
messages: {
|
||||
handler() {
|
||||
this.$nextTick(() => {
|
||||
this.updateView();
|
||||
});
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-container :deep(.q-scrollarea__content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-history {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-message-wrapper {
|
||||
padding: 4px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.chat-message.user {
|
||||
margin-left: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-message.user .message-bubble {
|
||||
color: var(--q-primary);
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 代码块样式优化 */
|
||||
.message-content :deep(pre) {
|
||||
margin: 4px 0;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-content :deep(pre::-webkit-scrollbar) {
|
||||
height: 3px; /* 与 markdown.css 保持一致 */
|
||||
}
|
||||
|
||||
.message-content :deep(code) {
|
||||
font-size: 12px;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* 代码块按钮样式 */
|
||||
.message-content :deep(.code-actions) {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.message-content :deep(pre:hover .code-actions) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.message-content :deep(.code-actions button) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--q-primary);
|
||||
background-color: var(--utools-bg-color);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.message-content :deep(.code-actions button:hover) {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.message-content :deep(.code-actions button i) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 链接样式 */
|
||||
.message-content :deep(a) {
|
||||
color: var(--q-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.message-content :deep(a:hover) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
@ -29,7 +29,7 @@
|
||||
>
|
||||
<draggable
|
||||
v-model="aiConfigs"
|
||||
item-key="name"
|
||||
item-key="id"
|
||||
handle=".drag-handle"
|
||||
:animation="200"
|
||||
class="config-list"
|
||||
@ -104,7 +104,7 @@
|
||||
dense
|
||||
dropdown-icon="refresh"
|
||||
>
|
||||
<q-list>
|
||||
<q-list dense>
|
||||
<q-item
|
||||
v-for="model in models"
|
||||
:key="model"
|
||||
@ -203,12 +203,12 @@ export default defineComponent({
|
||||
},
|
||||
addModel() {
|
||||
this.aiConfigs.push({
|
||||
id: getUniqueId(),
|
||||
apiType: this.apiToAdd,
|
||||
apiUrl: "",
|
||||
apiToken: "",
|
||||
model: "",
|
||||
name: "",
|
||||
id: getUniqueId(),
|
||||
});
|
||||
},
|
||||
getConfigListHeight() {
|
||||
@ -217,7 +217,12 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.aiConfigs = dbManager.getStorage("cfg_aiConfigs") || [];
|
||||
this.aiConfigs = (dbManager.getStorage("cfg_aiConfigs") || []).map(
|
||||
(config) => ({
|
||||
...config,
|
||||
id: config.id || getUniqueId(),
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="selector-wrapper">
|
||||
<q-select
|
||||
v-if="apiOptions.length > 0"
|
||||
:model-value="modelValue"
|
||||
@ -10,15 +10,25 @@
|
||||
dense
|
||||
options-dense
|
||||
filled
|
||||
class="ai-selector"
|
||||
:class="{ 'ai-selector--dense': dense }"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-badge color="primary" text-color="white" class="q-mr-sm q-pa-xs">
|
||||
<q-badge
|
||||
color="primary"
|
||||
text-color="white"
|
||||
class="q-mr-sm q-pa-xs"
|
||||
v-if="!dense"
|
||||
>
|
||||
模型
|
||||
</q-badge>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-btn icon="settings" dense flat @click.stop="showAIConfig = true" />
|
||||
</template>
|
||||
<template v-slot:selected-item="scope">
|
||||
<div class="ellipsis">{{ scope.opt.label }}</div>
|
||||
</template>
|
||||
</q-select>
|
||||
<q-btn
|
||||
dense
|
||||
@ -26,6 +36,7 @@
|
||||
class="full-width q-px-sm"
|
||||
icon="settings"
|
||||
label="配置AI接口"
|
||||
:size="dense ? 'xs' : 'md'"
|
||||
unelevated
|
||||
v-else
|
||||
@click="showAIConfig = true"
|
||||
@ -49,6 +60,10 @@ export default defineComponent({
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
dense: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -82,9 +97,57 @@ export default defineComponent({
|
||||
},
|
||||
mounted() {
|
||||
this.apiOptions = this.getApiOptions();
|
||||
if (!this.modelValue.id) {
|
||||
if (!this.modelValue?.id) {
|
||||
this.updateModelValue(this.apiOptions[0]?.value || {});
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.selector-wrapper {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ai-selector {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ai-selector--dense {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.q-field__native) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ai-selector--dense :deep(.q-field__control) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ai-selector--dense :deep(.q-field__control),
|
||||
.ai-selector--dense :deep(.q-field__control > *),
|
||||
.ai-selector--dense :deep(.q-field__native) {
|
||||
max-height: 26px !important;
|
||||
min-height: 26px !important;
|
||||
}
|
||||
|
||||
.ai-selector--dense :deep(.q-field__native) {
|
||||
padding: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.ai-selector--dense :deep(.q-field__append) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,11 +1,29 @@
|
||||
<template>
|
||||
<div class="code-editor" :style="{ height: height }">
|
||||
<div ref="editorContainer" class="editor-container" />
|
||||
<div class="placeholder-wrapper" v-show="showPlaceholder">
|
||||
<div class="placeholder">
|
||||
{{ placeholder }}
|
||||
<div
|
||||
class="editor-container"
|
||||
:class="{ 'with-assistant': showAIAssistant }"
|
||||
>
|
||||
<div ref="editorContainer" class="monaco-container" />
|
||||
<div class="placeholder-wrapper" v-show="showPlaceholder">
|
||||
<div class="placeholder">
|
||||
{{ placeholder }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI助手侧边栏 -->
|
||||
<Transition name="slide">
|
||||
<AIAssistantSideBar
|
||||
v-if="showAIAssistant"
|
||||
class="ai-assistant-panel"
|
||||
:code="modelValue"
|
||||
:language="language"
|
||||
@close="toggleAIAssistant(false)"
|
||||
@update-code="updateEditorValue"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<!-- AI助手按钮 -->
|
||||
<div class="ai-button-wrapper">
|
||||
<q-btn
|
||||
@ -13,19 +31,11 @@
|
||||
dense
|
||||
color="primary"
|
||||
icon="smart_toy"
|
||||
@click="showAIDialog = true"
|
||||
@click="toggleAIAssistant(true)"
|
||||
v-if="!showAIAssistant"
|
||||
>
|
||||
<q-tooltip>AI 助手</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
<!-- AI对话框 -->
|
||||
<q-dialog v-model="showAIDialog" position="right" seamless>
|
||||
<AIAssistantDialog
|
||||
:code="modelValue"
|
||||
:language="language"
|
||||
@update-code="setEditorValue"
|
||||
/>
|
||||
</q-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -33,7 +43,8 @@
|
||||
import * as monaco from "monaco-editor";
|
||||
import importAll from "js/common/importAll.js";
|
||||
import { defineComponent } from "vue";
|
||||
import AIAssistantDialog from "components/ai/AIAssistantDialog.vue";
|
||||
import AIAssistantSideBar from "components/ai/AIAssistantSideBar.vue";
|
||||
import editorOptions from "js/options/editorOptions.js";
|
||||
|
||||
// 批量导入关键字补全
|
||||
let languageCompletions = importAll(
|
||||
@ -61,7 +72,7 @@ const typeDefinitions = {
|
||||
export default defineComponent({
|
||||
name: "CodeEditor",
|
||||
components: {
|
||||
AIAssistantDialog,
|
||||
AIAssistantSideBar,
|
||||
},
|
||||
props: {
|
||||
// v-model 绑定值
|
||||
@ -100,65 +111,16 @@ export default defineComponent({
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue", "update:cursorPosition"],
|
||||
emits: [
|
||||
"update:modelValue",
|
||||
"update:cursorPosition",
|
||||
"saveHistory",
|
||||
"requestFullScreen",
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
resizeTimeout: null,
|
||||
defaultOptions: {
|
||||
value: "",
|
||||
// 自动布局
|
||||
automaticLayout: true,
|
||||
// 折叠策略
|
||||
foldingStrategy: "indentation",
|
||||
// 自动关闭括号
|
||||
autoClosingBrackets: true,
|
||||
// 制表符大小
|
||||
tabSize: 2,
|
||||
// 最小化
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
// 自动格式化
|
||||
formatOnType: true,
|
||||
// 自动格式化
|
||||
formatOnPaste: true,
|
||||
// 自动缩进
|
||||
autoIndent: "full",
|
||||
// 滚动超出最后一行
|
||||
scrollBeyondLastLine: false,
|
||||
// 字体大小
|
||||
fontSize: 14,
|
||||
// 行号
|
||||
lineNumbers: "on",
|
||||
// 行号最小字符数
|
||||
lineNumbersMinChars: 3,
|
||||
// 行号
|
||||
renderLineNumbers: "on",
|
||||
// 行装饰宽度
|
||||
lineDecorationsWidth: 0,
|
||||
// 圆角
|
||||
roundedSelection: false,
|
||||
// 行高亮
|
||||
renderLineHighlight: "all",
|
||||
// 仅在聚焦时高亮行
|
||||
renderLineHighlightOnlyWhenFocus: true,
|
||||
// 隐藏光标
|
||||
hideCursorInOverviewRuler: true,
|
||||
// 隐藏概览边框
|
||||
overviewRulerBorder: false,
|
||||
// 隐藏概览线
|
||||
overviewRulerLanes: 0,
|
||||
// 滚动条
|
||||
scrollBars: {
|
||||
vertical: "visible",
|
||||
horizontal: "visible",
|
||||
},
|
||||
// 只读
|
||||
readOnly: false,
|
||||
// 光标样式
|
||||
cursorStyle: "line",
|
||||
},
|
||||
showAIDialog: false,
|
||||
showAIAssistant: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@ -212,7 +174,7 @@ export default defineComponent({
|
||||
const language = this.getHighlighter(this.language);
|
||||
|
||||
const options = {
|
||||
...this.defaultOptions,
|
||||
...editorOptions,
|
||||
...this.options,
|
||||
value: this.modelValue || "",
|
||||
language,
|
||||
@ -419,8 +381,17 @@ export default defineComponent({
|
||||
formatDocument() {
|
||||
editor.getAction("editor.action.formatDocument").run();
|
||||
},
|
||||
setEditorValue(value) {
|
||||
editor.setValue(value);
|
||||
updateEditorValue(type, value) {
|
||||
if (type === "replace") {
|
||||
editor.setValue(value);
|
||||
} else if (type === "insert") {
|
||||
this.repacleEditorSelection(value);
|
||||
}
|
||||
this.$emit("saveHistory", value);
|
||||
},
|
||||
toggleAIAssistant(value) {
|
||||
this.showAIAssistant = value;
|
||||
this.$emit("requestFullScreen", value);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
@ -437,13 +408,47 @@ export default defineComponent({
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.editor-container.with-assistant {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.monaco-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ai-assistant-panel {
|
||||
width: 45%;
|
||||
height: 100%;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.slide-enter-active,
|
||||
.slide-leave-active {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.slide-enter-from,
|
||||
.slide-leave-to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.ai-button-wrapper {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 30px;
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
.placeholder-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -459,11 +464,4 @@ export default defineComponent({
|
||||
user-select: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.ai-button-wrapper {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 30px;
|
||||
z-index: 500;
|
||||
}
|
||||
</style>
|
||||
|
@ -43,7 +43,11 @@
|
||||
<div class="text-h6">历史记录</div>
|
||||
<q-icon name="help_outline" class="q-ml-sm">
|
||||
<q-tooltip>
|
||||
在初次进入编辑器、点击运行、保存按钮以及在此处进行恢复操作后,会自动保存当时的命令,方便进行回溯。<br />
|
||||
以下操作会自动保存历史记录:<br />
|
||||
1.初次进入编辑器;<br />
|
||||
2.点击运行、保存按钮;<br />
|
||||
3.在此处进行恢复操作。<br />
|
||||
4.AI助手自动提交代码后。<br />
|
||||
注意:<br />
|
||||
1.超过5k的内容不会保存;<br />
|
||||
2.数据是临时保存的,且不会多端同步;<br />
|
||||
|
@ -7,7 +7,6 @@
|
||||
background-color: #f6f8fa;
|
||||
padding: 1em;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
margin: 2px 0;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
@ -46,6 +45,7 @@
|
||||
.markdown h6 {
|
||||
margin: 2px 0;
|
||||
font-weight: 600;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
@ -77,8 +77,8 @@
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.markdown code ::-webkit-scrollbar {
|
||||
height: 5px;
|
||||
.markdown pre::-webkit-scrollbar {
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.markdown a {
|
||||
|
@ -3,8 +3,8 @@
|
||||
.q-field--filled:not(.q-textarea) .q-field__control,
|
||||
.q-field--filled:not(.q-textarea) .q-field__control>*,
|
||||
.q-field--filled:not(.q-field--labeled):not(.q-textarea) .q-field__native {
|
||||
max-height: 36px !important;
|
||||
min-height: 36px !important;
|
||||
max-height: 36px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.q-field--filled .q-field__control,
|
||||
|
54
src/js/options/editorOptions.js
Normal file
54
src/js/options/editorOptions.js
Normal file
@ -0,0 +1,54 @@
|
||||
export default {
|
||||
value: "",
|
||||
// 自动布局
|
||||
automaticLayout: true,
|
||||
// 折叠策略
|
||||
foldingStrategy: "indentation",
|
||||
// 自动关闭括号
|
||||
autoClosingBrackets: true,
|
||||
// 制表符大小
|
||||
tabSize: 2,
|
||||
// 最小化
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
// 自动格式化
|
||||
formatOnType: true,
|
||||
// 自动格式化
|
||||
formatOnPaste: true,
|
||||
// 自动缩进
|
||||
autoIndent: "full",
|
||||
// 滚动超出最后一行
|
||||
scrollBeyondLastLine: false,
|
||||
// 字体大小
|
||||
fontSize: 14,
|
||||
// 行号
|
||||
lineNumbers: "on",
|
||||
// 行号最小字符数
|
||||
lineNumbersMinChars: 3,
|
||||
// 行号
|
||||
renderLineNumbers: "on",
|
||||
// 行装饰宽度
|
||||
lineDecorationsWidth: 0,
|
||||
// 圆角
|
||||
roundedSelection: false,
|
||||
// 行高亮
|
||||
renderLineHighlight: "all",
|
||||
// 仅在聚焦时高亮行
|
||||
renderLineHighlightOnlyWhenFocus: true,
|
||||
// 隐藏光标
|
||||
hideCursorInOverviewRuler: true,
|
||||
// 隐藏概览边框
|
||||
overviewRulerBorder: false,
|
||||
// 隐藏概览线
|
||||
overviewRulerLanes: 0,
|
||||
// 滚动条
|
||||
scrollBars: {
|
||||
vertical: "visible",
|
||||
horizontal: "visible",
|
||||
},
|
||||
// 只读
|
||||
readOnly: false,
|
||||
// 光标样式
|
||||
cursorStyle: "line",
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user