Merge branch 'main' of https://github.com/xerrors/Yuxi-Know
This commit is contained in:
commit
8e7cf7c7a5
17
README.md
17
README.md
@ -1,6 +1,8 @@
|
||||
# 语析 - 基于大模型的知识库与知识图谱智能体开发平台
|
||||
# 📚 语析 - 基于大模型的知识库与知识图谱智能体开发平台
|
||||
|
||||
<div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://trendshift.io/repositories/15845" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15845" alt="Yuxi-Know | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
[](https://github.com/xerrors/Yuxi-Know/tree/v0.3.0)
|
||||
[](https://github.com/xerrors/Yuxi-Know/blob/main/docker-compose.yml)
|
||||
@ -9,7 +11,11 @@
|
||||
[](https://deepwiki.com/xerrors/Yuxi-Know)
|
||||
[](https://zread.ai/xerrors/Yuxi-Know)
|
||||
[](https://www.bilibili.com/video/BV1DF14BTETq/)
|
||||

|
||||
|
||||
<br/>
|
||||
|
||||
📄 [**文档中心**](https://xerrors.github.io/Yuxi-Know/) |
|
||||
📽️ [**视频演示**](https://www.bilibili.com/video/BV1DF14BTETq/)
|
||||
|
||||
</div>
|
||||
|
||||
@ -20,11 +26,6 @@
|
||||
|
||||
<img width="1632" height="392" alt="image" src="https://github.com/user-attachments/assets/ec381fde-53dd-4845-a79f-116b823fe989" />
|
||||
|
||||
|
||||
> [!tip]
|
||||
> 详细文档请查看全新的 📄[**文档中心**](https://xerrors.github.io/Yuxi-Know/),
|
||||
> 哔哩哔哩观看 📽️[**视频演示**](https://www.bilibili.com/video/BV1DF14BTETq/)
|
||||
|
||||
---
|
||||
|
||||
**🎉 最新动态**
|
||||
|
||||
@ -53,4 +53,8 @@ YUXI_BRAND_FILE_PATH=src/config/static/info.local.yaml
|
||||
|
||||
```
|
||||
|
||||
**此外**,`web/src/stores/theme.js` 中也包含了主题相关的配置(需要修改 `colorPrimary`),可根据需要修改。
|
||||
**此外**,`web/src/stores/theme.js` 中也包含了主题相关的配置(需要修改 `colorPrimary`),可根据需要修改。
|
||||
|
||||
## 修改首页
|
||||
|
||||
首页提供了一个插槽组件 `web/src/components/ProjectOverview.vue`,可以在该组件中自定义项目介绍,当前为空文件。(借助 AI 编程可以设计出更好看的首页的)
|
||||
|
||||
@ -362,38 +362,4 @@ async def get_database_mindmap(db_id: str, current_user: User = Depends(get_admi
|
||||
raise HTTPException(status_code=500, detail=f"获取思维导图失败: {str(e)}")
|
||||
|
||||
|
||||
@mindmap.post("/database/{db_id}")
|
||||
async def save_database_mindmap(
|
||||
db_id: str,
|
||||
mindmap: dict = Body(..., description="思维导图数据"),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
):
|
||||
"""
|
||||
保存思维导图到知识库
|
||||
|
||||
Args:
|
||||
db_id: 知识库ID
|
||||
mindmap: 思维导图数据
|
||||
|
||||
Returns:
|
||||
保存结果
|
||||
"""
|
||||
try:
|
||||
# 检查知识库是否存在
|
||||
db_info = knowledge_base.get_database_info(db_id)
|
||||
if not db_info:
|
||||
raise HTTPException(status_code=404, detail=f"知识库 {db_id} 不存在")
|
||||
|
||||
# TODO: 将思维导图保存到知识库元数据中
|
||||
# 这里需要实现一个方法来更新知识库的元数据
|
||||
|
||||
return {
|
||||
"message": "success",
|
||||
"db_id": db_id,
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"保存思维导图失败: {e}, {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"保存思维导图失败: {str(e)}")
|
||||
|
||||
@ -13,12 +13,16 @@ class Context(BaseContext):
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "工具",
|
||||
"options": gen_tool_info(get_tools()), # 这里的选择是所有的工具
|
||||
"options": lambda: gen_tool_info(get_tools()), # 这里的选择是所有的工具
|
||||
"description": "工具列表",
|
||||
},
|
||||
)
|
||||
|
||||
mcps: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={"name": "MCP服务器", "options": list(MCP_SERVERS.keys()), "description": "MCP服务器列表"},
|
||||
metadata={
|
||||
"name": "MCP服务器",
|
||||
"options": lambda: list(MCP_SERVERS.keys()),
|
||||
"description": "MCP服务器列表",
|
||||
},
|
||||
)
|
||||
|
||||
@ -6,7 +6,6 @@ import requests
|
||||
from langchain.tools import tool
|
||||
|
||||
from src.agents.common import get_buildin_tools
|
||||
from src.agents.common.toolkits.mysql import get_mysql_tools
|
||||
from src.storage.minio import aupload_file_to_minio
|
||||
from src.utils import logger
|
||||
|
||||
@ -52,5 +51,4 @@ def get_tools() -> list[Any]:
|
||||
"""获取所有可运行的工具(给大模型使用)"""
|
||||
tools = get_buildin_tools()
|
||||
tools.append(text_to_img_demo)
|
||||
tools.extend(get_mysql_tools())
|
||||
return tools
|
||||
|
||||
@ -107,10 +107,14 @@ class BaseContext:
|
||||
# 提取 Annotated 的元数据
|
||||
template_metadata = cls._extract_template_metadata(field_type)
|
||||
|
||||
options = f.metadata.get("options", [])
|
||||
if callable(options):
|
||||
options = options()
|
||||
|
||||
configurable_items[f.name] = {
|
||||
"type": type_name,
|
||||
"name": f.metadata.get("name", f.name),
|
||||
"options": f.metadata.get("options", []),
|
||||
"options": options,
|
||||
"default": f.default
|
||||
if f.default is not MISSING
|
||||
else f.default_factory()
|
||||
|
||||
@ -16,15 +16,15 @@ branding:
|
||||
|
||||
features:
|
||||
- label: "GitHub Stars"
|
||||
value: "2600+"
|
||||
value: "3000+"
|
||||
description: "开发者社区的认可与支持"
|
||||
icon: "stars"
|
||||
- label: "已解决 Issues"
|
||||
value: "210+"
|
||||
value: "250+"
|
||||
description: "持续改进和问题解决能力"
|
||||
icon: "issues"
|
||||
- label: "累计 Commits"
|
||||
value: "1000+"
|
||||
value: "1100+"
|
||||
description: "活跃的开发迭代和功能更新"
|
||||
icon: "commits"
|
||||
- label: "开源协议"
|
||||
|
||||
@ -22,9 +22,6 @@ except ImportError:
|
||||
def validate_database_schema(db_path):
|
||||
return True, []
|
||||
|
||||
# TODO:[已完成]为DBManager添加异步支持
|
||||
# TODO:[已完成]为DBManager添加单例模式
|
||||
|
||||
|
||||
class DBManager(metaclass=SingletonMeta):
|
||||
"""数据库管理器 - 提供异步数据库连接和会话管理"""
|
||||
|
||||
@ -49,18 +49,6 @@ export const mindmapApi = {
|
||||
*/
|
||||
getByDatabase: async (dbId) => {
|
||||
return apiAdminGet(`/api/mindmap/database/${dbId}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存思维导图到知识库
|
||||
* @param {string} dbId - 知识库ID
|
||||
* @param {Object} mindmapData - 思维导图数据
|
||||
* @returns {Promise} - 保存结果
|
||||
*/
|
||||
saveToDatabase: async (dbId, mindmapData) => {
|
||||
return apiAdminPost(`/api/mindmap/database/${dbId}`, {
|
||||
mindmap: mindmapData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1043,7 +1043,7 @@ watch(conversations, () => {
|
||||
|
||||
.floating-sidebar {
|
||||
position: absolute !important;
|
||||
z-index: 100;
|
||||
z-index: 101;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
@ -221,10 +221,6 @@
|
||||
<a-button @click="saveConfig" class="save-btn" :class="{'changed': agentStore.hasConfigChanges}">
|
||||
保存配置
|
||||
</a-button>
|
||||
<!-- TODO:BUG 目前有 bug 暂时不展示 -->
|
||||
<!-- <a-button @click="resetConfig" class="reset-btn">
|
||||
重置
|
||||
</a-button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -424,6 +420,10 @@ const getToolNameById = (toolId) => {
|
||||
const openToolsModal = async () => {
|
||||
console.log("availableTools.value", availableTools.value)
|
||||
try {
|
||||
// 强制刷新智能体详情以获取最新工具列表
|
||||
if (selectedAgentId.value) {
|
||||
await agentStore.fetchAgentDetail(selectedAgentId.value, true);
|
||||
}
|
||||
selectedTools.value = [...(agentConfig.value?.tools || [])];
|
||||
toolsModalOpen.value = true;
|
||||
} catch (error) {
|
||||
|
||||
@ -39,17 +39,13 @@
|
||||
:key="index"
|
||||
class="todo-item"
|
||||
>
|
||||
<span class="todo-status" :class="todo.status">
|
||||
<svg v-if="todo.status === 'completed'" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
</svg>
|
||||
<svg v-else-if="todo.status === 'in_progress'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="icon spinning">
|
||||
<circle cx="12" cy="12" r="10" stroke-dasharray="31.416" stroke-dashoffset="31.416" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="icon">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="todo-status">
|
||||
<CheckCircleOutlined v-if="todo.status === 'completed'" class="icon completed" />
|
||||
<SyncOutlined v-else-if="todo.status === 'in_progress'" class="icon in-progress" spin />
|
||||
<ClockCircleOutlined v-else-if="todo.status === 'pending'" class="icon pending" />
|
||||
<CloseCircleOutlined v-else-if="todo.status === 'cancelled'" class="icon cancelled" />
|
||||
<QuestionCircleOutlined v-else class="icon unknown" />
|
||||
</div>
|
||||
<span class="todo-text">{{ todo.content }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -110,6 +106,13 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Download } from 'lucide-vue-next';
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
SyncOutlined,
|
||||
ClockCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
QuestionCircleOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@ -367,59 +370,19 @@ const emitRefresh = () => {
|
||||
|
||||
.todo-status {
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 2px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
|
||||
.icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.spinning {
|
||||
animation: spin 1.5s linear infinite;
|
||||
stroke-dasharray: 31.416;
|
||||
stroke-dashoffset: 0;
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
background: var(--color-success-50);
|
||||
color: var(--color-success-700);
|
||||
|
||||
.icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&.in_progress {
|
||||
background: var(--color-warning-50);
|
||||
color: var(--color-warning-700);
|
||||
}
|
||||
|
||||
&.pending {
|
||||
background: var(--gray-100);
|
||||
color: var(--gray-500);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dashoffset: 15.708;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
stroke-dashoffset: 0;
|
||||
font-size: 16px;
|
||||
|
||||
&.completed { color: #52c41a; }
|
||||
&.in-progress { color: #1890ff; }
|
||||
&.pending { color: #faad14; }
|
||||
&.cancelled { color: #ff4d4f; }
|
||||
&.unknown { color: var(--gray-400); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
web/src/components/ProjectOverview.vue
Normal file
2
web/src/components/ProjectOverview.vue
Normal file
@ -0,0 +1,2 @@
|
||||
<template>
|
||||
</template>
|
||||
118
web/src/components/ToolCallingResult/TodoListResult.vue
Normal file
118
web/src/components/ToolCallingResult/TodoListResult.vue
Normal file
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="todo-list-result">
|
||||
<!-- <div class="todo-header">
|
||||
<h4><UnorderedListOutlined /> 待办事项</h4>
|
||||
</div> -->
|
||||
<div class="todo-content">
|
||||
<div v-for="(item, index) in data" :key="index" class="todo-item">
|
||||
<div class="todo-status">
|
||||
<CheckCircleOutlined v-if="item.status === 'completed'" class="icon completed" />
|
||||
<SyncOutlined v-else-if="item.status === 'in_progress'" class="icon in-progress" spin />
|
||||
<ClockCircleOutlined v-else-if="item.status === 'pending'" class="icon pending" />
|
||||
<CloseCircleOutlined v-else-if="item.status === 'cancelled'" class="icon cancelled" />
|
||||
<QuestionCircleOutlined v-else class="icon unknown" />
|
||||
</div>
|
||||
<div class="todo-text" :class="{ completed: item.status === 'completed', cancelled: item.status === 'cancelled' }">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
UnorderedListOutlined,
|
||||
CheckCircleOutlined,
|
||||
SyncOutlined,
|
||||
ClockCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
QuestionCircleOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.todo-list-result {
|
||||
background: var(--gray-0);
|
||||
border-radius: 8px;
|
||||
// border: 1px solid var(--gray-200);
|
||||
overflow: hidden;
|
||||
|
||||
.todo-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
background: var(--gray-25);
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--main-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.todo-content {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.todo-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
background: var(--gray-50);
|
||||
border: 1px solid var(--gray-100);
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--gray-100);
|
||||
}
|
||||
|
||||
.todo-status {
|
||||
padding-top: 2px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.icon {
|
||||
font-size: 16px;
|
||||
|
||||
&.completed { color: #52c41a; }
|
||||
&.in-progress { color: #1890ff; }
|
||||
&.pending { color: #faad14; }
|
||||
&.cancelled { color: #ff4d4f; }
|
||||
&.unknown { color: var(--gray-400); }
|
||||
}
|
||||
}
|
||||
|
||||
.todo-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--gray-800);
|
||||
word-break: break-word;
|
||||
|
||||
&.completed {
|
||||
text-decoration: line-through;
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
&.cancelled {
|
||||
text-decoration: line-through;
|
||||
color: var(--gray-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -19,6 +19,12 @@
|
||||
ref="graphResultRef"
|
||||
/>
|
||||
|
||||
<!-- 待办事项结果 -->
|
||||
<TodoListResult
|
||||
v-else-if="isTodoListResult"
|
||||
:data="todoListData"
|
||||
/>
|
||||
|
||||
<!-- 计算器结果 -->
|
||||
<CalculatorResult
|
||||
v-else-if="isCalculatorResult"
|
||||
@ -49,6 +55,7 @@ import WebSearchResult from './WebSearchResult.vue'
|
||||
import KnowledgeBaseResult from './KnowledgeBaseResult.vue'
|
||||
import KnowledgeGraphResult from './KnowledgeGraphResult.vue'
|
||||
import CalculatorResult from './CalculatorResult.vue'
|
||||
import TodoListResult from './TodoListResult.vue'
|
||||
import { useAgentStore } from '@/stores/agent';
|
||||
|
||||
const agentStore = useAgentStore()
|
||||
@ -81,6 +88,60 @@ const parsedData = computed(() => {
|
||||
return props.resultContent
|
||||
})
|
||||
|
||||
const todoListData = computed(() => {
|
||||
if (props.toolName !== 'write_todos') return []
|
||||
|
||||
const raw = props.resultContent
|
||||
|
||||
// 1. Try from parsedData (JSON object)
|
||||
const data = parsedData.value
|
||||
if (data && typeof data === 'object') {
|
||||
if (Array.isArray(data)) return data
|
||||
if (data.todos && Array.isArray(data.todos)) return data.todos
|
||||
}
|
||||
|
||||
// 2. Try parsing string if it matches specific pattern
|
||||
if (typeof raw === 'string') {
|
||||
let str = raw
|
||||
if (str.startsWith('Updated todo list to ')) {
|
||||
str = str.replace('Updated todo list to ', '')
|
||||
}
|
||||
|
||||
// Try regex parsing for Python-like string
|
||||
const items = []
|
||||
// Matches {'content': '...', 'status': '...'} with escaped quotes support
|
||||
// content might contain escaped quotes
|
||||
const contentRegex = /'content':\s*'((?:[^'\\]|\\.)*)'/
|
||||
const statusRegex = /'status':\s*'((?:[^'\\]|\\.)*)'/
|
||||
|
||||
// Split by "}, {" roughly, or just look for objects
|
||||
// Since it is a list of dicts, we can match individual dicts
|
||||
const dictRegex = /\{.*?\}/g
|
||||
const dictMatches = str.match(dictRegex)
|
||||
|
||||
if (dictMatches) {
|
||||
for (const dictStr of dictMatches) {
|
||||
const contentMatch = dictStr.match(contentRegex)
|
||||
const statusMatch = dictStr.match(statusRegex)
|
||||
|
||||
if (contentMatch && statusMatch) {
|
||||
items.push({
|
||||
content: contentMatch[1].replace(/\\'/g, "'").replace(/\\\\/g, "\\"),
|
||||
status: statusMatch[1]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (items.length > 0) return items
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
|
||||
const isTodoListResult = computed(() => {
|
||||
return props.toolName === 'write_todos' && todoListData.value.length > 0
|
||||
})
|
||||
|
||||
// 判断是否为网页搜索结果
|
||||
const isWebSearchResult = computed(() => {
|
||||
const toolNameLower = props.toolName.toLowerCase()
|
||||
|
||||
@ -146,11 +146,11 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
* 获取单个智能体的详细信息(包含配置选项)
|
||||
* @param {string} agentId - 智能体ID
|
||||
*/
|
||||
async function fetchAgentDetail(agentId) {
|
||||
async function fetchAgentDetail(agentId, forceRefresh = false) {
|
||||
if (!agentId) return
|
||||
|
||||
// 如果已经缓存了详细信息,直接返回
|
||||
if (agentDetails.value[agentId]) {
|
||||
// 如果已经缓存了详细信息且不强制刷新,直接返回
|
||||
if (!forceRefresh && agentDetails.value[agentId]) {
|
||||
return agentDetails.value[agentId]
|
||||
}
|
||||
|
||||
|
||||
@ -27,17 +27,17 @@ export const useInfoStore = defineStore('info', () => {
|
||||
// 计算属性 - 功能特性
|
||||
const features = computed(() => infoConfig.value.features || [{
|
||||
label: "GitHub Stars",
|
||||
value: "2600+",
|
||||
value: "3000+",
|
||||
description: "开发者社区的认可与支持",
|
||||
icon: "stars"
|
||||
}, {
|
||||
label: "已解决 Issues",
|
||||
value: "200+",
|
||||
value: "250+",
|
||||
description: "持续改进和问题解决能力",
|
||||
icon: "issues"
|
||||
}, {
|
||||
label: "累计 Commits",
|
||||
value: "1000+",
|
||||
value: "1100+",
|
||||
description: "活跃的开发迭代和功能更新",
|
||||
icon: "commits"
|
||||
}, {
|
||||
|
||||
@ -75,6 +75,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProjectOverview />
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<p class="copyright">{{ infoStore.footer?.copyright || '© 2025 All rights reserved' }}</p>
|
||||
@ -91,6 +93,7 @@ import { useInfoStore } from '@/stores/info'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import UserInfoComponent from '@/components/UserInfoComponent.vue'
|
||||
import ProjectOverview from '@/components/ProjectOverview.vue'
|
||||
import {
|
||||
BookText,
|
||||
Bug,
|
||||
@ -218,7 +221,7 @@ const actionLinks = computed(() => {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.home-container {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--main-900);
|
||||
@ -226,7 +229,6 @@ const actionLinks = computed(() => {
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.glass-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -266,8 +268,6 @@ const actionLinks = computed(() => {
|
||||
&:hover {
|
||||
color: var(--gray-900);
|
||||
|
||||
|
||||
|
||||
svg {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
@ -511,7 +511,7 @@ const actionLinks = computed(() => {
|
||||
.section {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
margin: 50px auto 0px auto;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user