Compare commits

...

7 Commits

Author SHA1 Message Date
03e1e5662d 重构部分代码 2025-04-02 19:57:39 +08:00
0df00ba33e 优化改写文案 2025-04-02 19:41:49 +08:00
f9b031e8da 实现改写文案标题 2025-04-02 19:28:39 +08:00
ccdbbabb1e 输出区调整完成 2025-04-02 19:03:15 +08:00
7181da5b7b 输入区样式调整完毕 2025-04-02 18:51:45 +08:00
7b056d476d 将输入区域提取为组件 2025-04-02 18:41:17 +08:00
e8c034937e 修复bug 2025-03-20 23:16:18 +08:00
19 changed files with 2170 additions and 1492 deletions

View File

@ -1,2 +1,16 @@
# LingTropy
减小包体
yarn global add depcheck
depcheck
然后yarn remove <unused-package>
还有配置修改
module.exports = {
files: [
"dist/**/*", // 只打包 dist 目录
"!node_modules/**/*" // 排除 node_modules
],
asar: true, // 使用 asar 压缩
};

View File

@ -3,7 +3,7 @@ const dotenv = require('dotenv')
const packageJson = require('../../package.json')
const baseConfig = {
productName: packageJson.name,
productName: '文案助手',
appId: packageJson.appId,
asar: true,
extends: null,

View File

@ -1,27 +1,28 @@
{
"name": "lingtropy",
"version": "1.0.0",
"version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lingtropy",
"version": "1.0.0",
"version": "1.0.2",
"license": "MIT",
"dependencies": {
"@mdi/font": "^7.4.47",
"axios": "^1.8.3",
"electron-store": "^10.0.1",
"lingtropy": "file:",
"lingtropy2": "file:",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-i18n": "^11.1.1",
"vue-router": "^4.5.0",
"vuetify": "^3.7.14",
"vutron": "file:"
"vuetify": "^3.7.14"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@types/electron-store": "^3.2.2",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"@vitejs/plugin-vue": "^5.2.1",
@ -41,7 +42,7 @@
"playwright": "^1.50.1",
"prettier": "^3.5.2",
"tree-kill": "^1.2.2",
"typescript": "5.7.3",
"typescript": "^5.8.2",
"vite": "^6.2.0",
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",
@ -1321,6 +1322,17 @@
"@types/ms": "*"
}
},
"node_modules/@types/electron-store": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/electron-store/-/electron-store-3.2.2.tgz",
"integrity": "sha512-N3X45mnsfnwmeZoXSZmeE7/Tne8kdbIKO1vQdbbEV04TzrMbWIeDVJJjnX2n5GH9O61zI612tet4s2jCZ55DXw==",
"deprecated": "This is a stub types definition. electron-store provides its own type definitions, so you do not need this installed.",
"dev": true,
"license": "MIT",
"dependencies": {
"electron-store": "*"
}
},
"node_modules/@types/eslint": {
"version": "8.56.11",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz",
@ -6333,6 +6345,10 @@
"resolved": "",
"link": true
},
"node_modules/lingtropy2": {
"resolved": "",
"link": true
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -8697,9 +8713,9 @@
}
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
@ -9151,10 +9167,6 @@
}
}
},
"node_modules/vutron": {
"resolved": "",
"link": true
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",

View File

@ -1,7 +1,7 @@
{
"name": "lingtropy",
"appId": "com.lingnite.lingtropy",
"version": "1.0.0",
"version": "1.0.2",
"description": "文案助手",
"homepage": "https://www.lingnite.com",
"author": "沈阳泠启网络科技有限公司",
@ -45,6 +45,8 @@
"@mdi/font": "^7.4.47",
"axios": "^1.8.3",
"electron-store": "^10.0.1",
"lingtropy": "file:",
"lingtropy2": "file:",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-i18n": "^11.1.1",
@ -53,6 +55,7 @@
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@types/electron-store": "^3.2.2",
"@typescript-eslint/eslint-plugin": "7.16.1",
"@typescript-eslint/parser": "7.16.1",
"@vitejs/plugin-vue": "^5.2.1",
@ -72,7 +75,7 @@
"playwright": "^1.50.1",
"prettier": "^3.5.2",
"tree-kill": "^1.2.2",
"typescript": "5.7.3",
"typescript": "^5.8.2",
"vite": "^6.2.0",
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",

View File

@ -0,0 +1,42 @@
// API 相关
export const API_ENDPOINTS = {
GET_BASE_URL: 'https://aqq-jbjsjuxivc.cn-hangzhou.fcapp.run',
FETCH_MODELS: 'https://get-model-list-vcwjgnvcld.cn-hangzhou.fcapp.run'
} as const
// AI 模型相关
export const AI_CONFIG = {
DEFAULT_MODEL: 'gpt-4o',
CONTENT_MAX_TOKENS: 1800, // 约900字
TITLE_MAX_TOKENS: 50, // 约20字
TEMPERATURE: 0.8,
MAX_RETRIES: 2,
TIMEOUT: 30000
} as const
// 提示词模板
export const PROMPTS = {
CONTENT_REWRITE: '你是一个小红书文案写手,能够熟练地根据用户的输入,改写成内容相近,但表达方式不同的新文案。你的文案中需要具备吸人眼球的钩子,能够牢牢抓住用户的注意力。请直接输出新的文案,不要输出其他任何提示性词语, 以纯文本的形式输出。注意输出的文案不要超过900字。',
TITLE_REWRITE: '你是一个小红书标题写手,能够熟练地根据用户的输入,改写成内容相近,但表达方式不同的新标题。你的标题中需要具备吸人眼球的钩子,能够牢牢抓住用户的注意力。请直接输出新的标题,不要输出其他任何提示性词语, 以纯文本的形式输出。注意输出的标题不要超过20字。'
} as const
// 存储键名
export const STORE_KEYS = {
API_KEY: 'api-key',
LAYOUT_MODE: 'layoutMode',
GENERATION_COUNT: 'generationCount'
} as const
// UI 相关
export const UI_CONFIG = {
DEFAULT_LAYOUT: 'list',
DEFAULT_COUNT: 3,
MAX_COUNT: 10,
MIN_COUNT: 1
} as const
// 应用信息
// export const APP_INFO = {
// VERSION: '1.0.0',
// NAME: 'LingTropy'
// } as const

View File

@ -3,7 +3,14 @@ import Constants from './utils/Constants'
import Store from 'electron-store'
import axios from 'axios'
import OpenAI from 'openai'
import { HttpsProxyAgent } from 'https-proxy-agent'
import {
API_ENDPOINTS,
AI_CONFIG,
PROMPTS,
STORE_KEYS,
APP_INFO
} from '../constants'
const store = new Store()
/*
* IPC Communications
@ -20,6 +27,7 @@ const store = new Store()
// }
// }
// const baseUrl = ""
export default class IPCs {
static initialize(): void {
// Get application version
@ -37,7 +45,7 @@ export default class IPCs {
ipcMain.handle('fetch-models', async () => {
try {
const response = await axios.get('https://get-model-list-vcwjgnvcld.cn-hangzhou.fcapp.run') // 从网络获取数据
const response = await axios.get(API_ENDPOINTS.FETCH_MODELS)
console.log('获取模型数据成功:', response.data)
return response.data.data
} catch (error) {
@ -48,7 +56,7 @@ export default class IPCs {
ipcMain.handle('getBaseUrl', async () => {
try {
const response = await axios.get('https://aqq-jbjsjuxivc.cn-hangzhou.fcapp.run')
const response = await axios.get(API_ENDPOINTS.GET_BASE_URL)
console.log('获取baseUrl成功:', response.data)
return response.data.data
} catch (error) {
@ -70,11 +78,10 @@ export default class IPCs {
ipcMain.handle('call-openai', async (event, baseURL, apiKey, model, count, rawArticle) => {
try {
const client = new OpenAI({
apiKey: apiKey, // 从环境变量中获取API密钥
baseURL: baseURL // 设置代理地址
apiKey: apiKey,
baseURL: baseURL
})
// 创建 count 个请求
const requests = Array.from({ length: count }, (_, index) => {
return client.chat.completions
.create({
@ -82,12 +89,13 @@ export default class IPCs {
messages: [
{
role: 'system',
content:
'你是一个小红书文案写手,能够熟练地根据用户的输入,改写成内容相近,但表达方式不同的新文案。你的文案中需要具备吸人眼球的钩子,能够牢牢抓住用户的注意力。请直接输出新的文案,不要输出其他任何提示性词语, 以纯文本的形式输出'
content: PROMPTS.CONTENT_REWRITE
},
{ role: 'user', content: rawArticle }
],
stream: true // 启用流式输出
max_tokens: AI_CONFIG.CONTENT_MAX_TOKENS,
temperature: AI_CONFIG.TEMPERATURE,
stream: true
})
.then(async (stream) => {
let fullResponse = ''
@ -113,6 +121,57 @@ export default class IPCs {
}
})
ipcMain.handle(
'call-openai-title',
async (event, baseUrl: string, apiKey: string, model: string, count: number, text: string) => {
try {
const client = new OpenAI({
baseURL: baseUrl,
apiKey: apiKey,
maxRetries: 2,
timeout: 30000
})
const requests = Array.from({ length: count }, (_, index) => {
return client.chat.completions
.create({
model: model,
messages: [
{
role: 'system',
content: '你是一个小红书标题写手,能够熟练地根据用户的输入,改写成内容相近,但表达方式不同的新标题。你的标题中需要具备吸人眼球的钩子,能够牢牢抓住用户的注意力。请直接输出新的标题,不要输出其他任何提示性词语, 以纯文本的形式输出。注意输出的标题不要超过20字。'
},
{ role: 'user', content: text }
],
max_tokens: 50, // 约20字
temperature: 0.8,
stream: true
})
.then(async (stream) => {
let fullResponse = ''
for await (const chunk of stream) {
const content = chunk.choices[0].delta.content || ''
fullResponse += content
// 实时发送每个请求的部分结果
event.sender.send('openai-partial-response', { index, content })
}
// 返回最终结果
return { index, response: fullResponse.trim() }
})
})
// 并发执行所有请求
const results = await Promise.all(requests)
// 返回所有请求的最终结果
return results
} catch (error) {
console.error('生成标题失败:', error)
throw error
}
}
)
ipcMain.handle('get-api-key', () => {
return store.get('api-key') || null
})
@ -144,5 +203,7 @@ export default class IPCs {
})
return dialogResult
})
}
}

View File

@ -10,13 +10,14 @@ import { createTray, hideWindow, showWindow } from './tray.ts'
const options = {
width: Constants.IS_DEV_ENV ? 1500 : 1250,
height: 850,
height: 900,
tray: {
// all optional values from DEFAULT_TRAY_OPTIONS can de defined here
enabled: true,
menu: false, // true, to use a tray menu ; false to toggle visibility on click on tray icon
trayWindow: false // true, to use a tray floating window attached to top try icon
}
},
title: '文案助手'
}
const exitApp = (mainWindow: BrowserWindow): void => {

View File

@ -19,6 +19,7 @@ app.on('ready', async () => {
*/
mainWindow = await createMainWindow()
// mainWindow.setTile("23")
})
app.on('activate', async () => {

View File

@ -15,7 +15,7 @@ export interface TrayOptions {
export default class Constants {
// Display app name (uppercase first letter)
static APP_NAME = name.charAt(0).toUpperCase() + name.slice(1)
static APP_NAME = '文案助手' //name.charAt(0).toUpperCase() + name.slice(1)
static APP_VERSION = version

View File

@ -12,7 +12,8 @@ const mainAvailChannels: string[] = [
'call-openai',
'openai-partial-response',
'get-api-key',
'set-api-key'
'set-api-key',
'call-openai-title'
]
const rendererAvailChannels: string[] = ['openai-partial-response', 'get-api-key', 'set-api-key']

View File

@ -0,0 +1,193 @@
<template>
<!-- 免责声明弹窗 -->
<div v-if="showDisclaimer" class="disclaimer-modal">
<div class="disclaimer-content">
<h2 class="disclaimer-title">免责声明</h2>
<div class="disclaimer-text">
<p>您正在使用本软件请您仔细阅读以下内容并确认</p>
<ol>
<li
>根据小红书平台的相关要求通过反复发布重复近似交易笔记或发布大量发布工业化低质量创作的交易笔记等方式获取流量的行为系违规行为</li
>
<li>本软件充分尊重原创者的著作权等知识产权仅为用户提供参考素材与灵感</li>
<li
>本软件郑重提醒用户请各用户遵守知识产权相关规定秉持真诚分享精神自行对在小红书平台及其他社交媒体平台发布的内容进行判断负责不要直接搬运原创作品严禁用于辅助违规后端操作引流</li
>
<li>对用户违反本声明所产生的后果炬梦私域与泠启科技不承担任何责任</li>
<li>本软件使用的ai仅供内部测试严禁生成违背各种法律法规或公序良俗的内容</li>
</ol>
<p>继续使用本工具即表示您已阅读并同意上述声明</p>
</div>
<div class="disclaimer-actions">
<label class="disclaimer-checkbox">
<input type="checkbox" v-model="doNotShowAgain" />
<span>不再显示</span>
</label>
<button @click="acceptDisclaimer" class="disclaimer-button">我已阅读并同意</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
//
const showDisclaimer = ref(false)
const doNotShowAgain = ref(false)
//
const checkDisclaimerStatus = async () => {
try {
const hideDisclaimer = await window.mainApi.invoke('store-get', 'hideDisclaimer')
showDisclaimer.value = !hideDisclaimer
// showDisclaimer.value = true
// console.log(':', showDisclaimer.value)
} catch (err) {
// console.error(':', err)
showDisclaimer.value = true //
}
}
//
const acceptDisclaimer = async () => {
if (doNotShowAgain.value) {
try {
await window.mainApi.invoke('store-set', 'hideDisclaimer', true)
} catch (err) {
console.error('保存免责声明偏好失败:', err)
}
}
showDisclaimer.value = false
}
onMounted(() => {
checkDisclaimerStatus()
})
</script>
<style scoped>
.disclaimer-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 90;
animation: fadeIn 0.3s ease;
}
.disclaimer-content {
background-color: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
width: 90%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
padding: 30px;
position: relative;
}
.disclaimer-title {
font-size: 24px;
font-weight: 700;
margin: 0 0 20px 0;
color: #2ecc71;
text-align: center;
border-bottom: 2px solid #f0f0f0;
padding-bottom: 15px;
}
.disclaimer-text {
margin-bottom: 25px;
line-height: 1.6;
color: #333;
}
.disclaimer-text p {
margin-bottom: 15px;
}
.disclaimer-text ol {
padding-left: 20px;
margin-bottom: 15px;
}
.disclaimer-text li {
margin-bottom: 10px;
}
.disclaimer-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
border-top: 1px solid #f0f0f0;
padding-top: 20px;
}
.disclaimer-checkbox {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
}
.disclaimer-checkbox input {
width: 18px;
height: 18px;
}
.disclaimer-button {
background: linear-gradient(135deg, #2ecc71, #27ae60);
border: none;
border-radius: 8px;
color: white;
cursor: pointer;
font-size: 16px;
font-weight: 600;
padding: 12px 24px;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
}
.disclaimer-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(46, 204, 113, 0.4);
}
@media (max-width: 1200px) {
.disclaimer-content {
width: 95%;
padding: 20px;
}
}
@media (max-width: 768px) {
.disclaimer-actions {
flex-direction: column;
gap: 15px;
}
.disclaimer-checkbox {
margin-bottom: 10px;
}
.disclaimer-button {
width: 100%;
}
}
@media (max-width: 480px) {
.disclaimer-content {
padding: 15px;
}
.disclaimer-title {
font-size: 20px;
}
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<div class="company-footer">
<div class="company-footer-content">
<p>炬梦私域X泠启科技</p>
</div>
</div>
</template>
<script setup>
//
</script>
<style scoped>
.company-footer {
position: fixed;
margin-top: 10px;
bottom: 0;
left: 0;
right: 0;
width: 100%;
padding: 15px 0;
text-align: center;
z-index: 100;
}
.company-footer-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.company-footer-content p {
margin: 0;
font-size: 24px;
font-weight: 800;
letter-spacing: 2px;
position: relative;
display: inline-block;
}
@media (max-width: 768px) {
.company-footer {
padding: 10px 0;
}
.company-footer-content p {
font-size: 17px;
}
}
</style>

View File

@ -12,7 +12,7 @@
<div class="logo-icon">AI</div>
</div>
<div class="header-content">
<h1 class="title">文案生成工具</h1>
<h1 class="title">文案助手</h1>
<p class="subtitle">智能改写提升文案质量</p>
</div>
<button @click="toggleSettings" class="settings-button">
@ -46,7 +46,7 @@
</div>
</div>
<div class="modal-footer">
<div class="copyright"> © 2025 沈阳泠启网络科技有限公司版权所有 </div>
<div class="copyright"> 仅供测试开发使用严禁对外销售 </div>
<div class="button-group">
<button @click="toggleSettings" class="cancel-button">取消</button>
<button @click="saveSettings" class="save-button">保存设置</button>

View File

@ -0,0 +1,468 @@
<template>
<div class="input-section">
<HeaderComponent v-model:tokenvalue="currentKey" />
<SelectModel v-model:modelvalue="currentModel" />
<div class="rewrite-mode-section">
<h2 class="section-title">
<span class="section-icon"></span>
改写模式
</h2>
<div class="mode-tabs">
<button
:class="{ active: mode === 'single' }"
@click="switchMode('single')"
class="mode-tab"
>
<span class="mode-icon">🔄</span>
单次改写
</button>
<button
:class="{ active: mode === 'batch' }"
@click="switchMode('batch')"
class="mode-tab"
>
<span class="mode-icon">🔀</span>
批量改写
</button>
</div>
<transition name="fade">
<div v-if="mode === 'batch'" class="generation-count">
<label for="count-input">生成数量:</label>
<div class="count-control">
<button
:disabled="generationCount <= 1"
@click="decrementCount"
class="count-button"
>-</button>
<input
id="count-input"
v-model="generationCount"
@input="validateInput"
type="number"
class="count-input"
/>
<button
:disabled="generationCount >= ARTICLE_MAX_COUNT"
@click="incrementCount"
class="count-button"
>+</button>
</div>
</div>
</transition>
</div>
<div class="original-text-section">
<h2 class="section-title">
<span class="section-icon">📝</span>
原始文案
<span v-if="originalText" class="char-count">{{ originalText.length }} </span>
</h2>
<textarea
v-model="originalText"
class="text-input"
placeholder="请在此输入需要改写的文本..."
></textarea>
</div>
<div class="button-group">
<button
:disabled="isGenerating || isGeneratingTitle"
@click="handleRewrite('content')"
class="rewrite-button"
>
<span v-if="isGenerating" class="loading-spinner"></span>
<span v-else>改写文案</span>
</button>
<button
:disabled="isGenerating || isGeneratingTitle"
@click="handleRewrite('title')"
class="rewrite-button title-button"
>
<span v-if="isGeneratingTitle" class="loading-spinner"></span>
<span v-else>改写标题</span>
</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { ARTICLE_MAX_COUNT } from '../constants/constants'
import HeaderComponent from './HeaderComponent.vue'
import SelectModel from './SelectModelComponent.vue'
const { isGenerating, isGeneratingTitle } = defineProps({
isGenerating: {
type: Boolean,
default: false
},
isGeneratingTitle: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['rewrite', 'update:model'])
const mode = ref('single')
const originalText = ref('')
const generationCount = ref(3)
const currentModel = ref('gpt-4o')
const currentKey = ref('')
onMounted(async () => {
//
const count = await window.mainApi.invoke('store-get', 'generationCount')
if (count) {
generationCount.value = count
}
})
watch(currentModel, (newValue) => {
emit('update:model', newValue)
})
const validateInput = () => {
if (!originalText.value.trim()) {
return false
}
if (generationCount.value < 1) {
setGenerationCount(1)
} else if (generationCount.value > ARTICLE_MAX_COUNT) {
setGenerationCount(ARTICLE_MAX_COUNT)
}
return true
}
const setGenerationCount = async (value) => {
generationCount.value = value
await window.mainApi.invoke('store-set', 'generationCount', value)
}
const switchMode = (newMode) => {
if (mode.value !== newMode) {
mode.value = newMode
}
}
const incrementCount = () => {
if (generationCount.value < ARTICLE_MAX_COUNT) {
setGenerationCount(generationCount.value + 1)
}
}
const decrementCount = () => {
if (generationCount.value > 1) {
setGenerationCount(generationCount.value - 1)
}
}
const handleRewrite = async (type) => {
if (!validateInput()) {
showToast('请输入需要改写的文本')
return
}
emit('rewrite', {
mode: mode.value,
text: originalText.value,
count: mode.value === 'single' ? 1 : generationCount.value,
model: currentModel.value,
key: currentKey.value,
type
})
}
const showToast = (message) => {
const toast = document.createElement('div')
toast.className = 'toast-message'
toast.textContent = message
document.body.appendChild(toast)
setTimeout(() => {
toast.classList.add('show')
setTimeout(() => {
toast.classList.remove('show')
setTimeout(() => {
document.body.removeChild(toast)
}, 300)
}, 2000)
}, 10)
}
</script>
<style scoped>
.input-section {
flex: 0 0 40%;
max-width: 600px;
min-width: 320px;
background-color: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 28px;
display: flex;
flex-direction: column;
overflow-y: auto;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.95);
}
.section-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 16px 0;
color: #333;
display: flex;
align-items: center;
gap: 8px;
}
.section-icon {
font-size: 20px;
}
.rewrite-mode-section {
margin-bottom: 24px;
}
.mode-tabs {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
.mode-tab {
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 10px;
color: #333;
cursor: pointer;
flex: 1;
font-size: 16px;
padding: 14px;
text-align: center;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.mode-tab:hover {
border-color: #2ecc71;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.mode-tab.active {
background: linear-gradient(135deg, #2ecc71, #27ae60);
border-color: transparent;
color: white;
box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
}
.mode-icon {
font-size: 18px;
}
.generation-count {
display: flex;
align-items: center;
margin-bottom: 24px;
gap: 16px;
padding: 16px;
background-color: #f9f9f9;
border-radius: 10px;
border-left: 4px solid #2ecc71;
}
.count-control {
display: flex;
align-items: center;
}
.count-button {
width: 36px;
height: 36px;
border: 1px solid #e0e0e0;
background-color: white;
color: #333;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.count-button:first-child {
border-radius: 8px 0 0 8px;
}
.count-button:last-child {
border-radius: 0 8px 8px 0;
}
.count-button:hover:not(:disabled) {
background-color: #f0f0f0;
}
.count-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.count-input {
border: 1px solid #e0e0e0;
border-left: none;
border-right: none;
padding: 8px 0;
width: 50px;
font-size: 16px;
text-align: center;
height: 36px;
}
.original-text-section {
margin-bottom: 24px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.char-count {
font-size: 14px;
color: #666;
font-weight: normal;
margin-left: auto;
background-color: #f0f0f0;
padding: 2px 8px;
border-radius: 12px;
}
.text-input {
border: 1px solid #e0e0e0;
border-radius: 10px;
font-family: inherit;
font-size: 16px;
padding: 16px;
resize: none;
width: 100%;
flex-grow: 1;
min-height: 150px;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
transition: border-color 0.3s, box-shadow 0.3s;
background-color: #f9f9f9;
}
.text-input:focus {
outline: none;
border-color: #2ecc71;
box-shadow: 0 0 0 3px rgba(46, 204, 113, 0.2);
background-color: white;
}
.button-group {
display: flex;
gap: 12px;
margin-top: 16px;
}
.rewrite-button {
flex: 1;
background: linear-gradient(135deg, #2ecc71, #27ae60);
border: none;
border-radius: 10px;
color: white;
cursor: pointer;
font-size: 16px;
font-weight: 600;
padding: 16px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
box-shadow: 0 4px 12px rgba(46, 204, 113, 0.3);
}
.rewrite-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(46, 204, 113, 0.4);
}
.rewrite-button:active:not(:disabled) {
transform: translateY(1px);
box-shadow: 0 2px 8px rgba(46, 204, 113, 0.3);
}
.rewrite-button:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.title-button {
background: linear-gradient(135deg, #3498db, #2980b9);
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
}
.title-button:hover:not(:disabled) {
box-shadow: 0 6px 16px rgba(52, 152, 219, 0.4);
}
.title-button:active:not(:disabled) {
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3);
}
@media (max-width: 1200px) {
.input-section {
flex: none;
width: 100%;
max-width: 100%;
}
}
@media (max-width: 768px) {
.input-section {
padding: 20px;
border-radius: 12px;
}
.mode-tabs {
flex-direction: column;
}
.generation-count {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
}
@media (max-width: 480px) {
.input-section {
padding: 16px;
}
}
</style>

View File

@ -0,0 +1,543 @@
<template>
<transition name="fade">
<div v-if="(results?.length > 0) || isGenerating" class="result-section">
<div class="result-header-bar">
<h2 class="section-title">
<span class="section-icon"></span>
改写结果
</h2>
<div class="result-stats">
<div class="layout-toggle">
<button
@click="$emit('update:layout', 'list')"
:class="{ active: layoutMode === 'list' }"
class="layout-button"
>
<span class="layout-icon">📋</span>
</button>
<button
@click="$emit('update:layout', 'grid')"
:class="{ active: layoutMode === 'grid' }"
class="layout-button"
>
<span class="layout-icon">📊</span>
</button>
</div>
<span class="model-badge">{{ model }}</span>
<span class="count-badge">{{ results?.length || 0 }} 个结果</span>
</div>
</div>
<div v-if="isGenerating && results.length === 0" class="generating-placeholder">
<div class="generating-animation">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
<p>正在使用 {{ model }} 生成改写结果...</p>
</div>
<div v-else class="results-container">
<div :class="['results-list', layoutMode === 'grid' ? 'grid-layout' : '']">
<div
v-for="(text, index) in results"
:key="index"
:class="{
'expanded-result': expandedResults[index],
'grid-item': layoutMode === 'grid'
}"
:style="{ animationDelay: `${index * 0.1}s` }"
class="result-block"
>
<div class="result-header">
<div class="result-number">
结果 #{{ index + 1 }}
<span class="char-count">{{ text.length }} </span>
</div>
<div class="result-actions">
<button @click="handleCopy(text, index)" class="action-button copy-button">
<span v-if="copiedIndex === index">已复制!</span>
<span v-else>复制</span>
</button>
<button @click="toggleExpand(index)" class="action-button expand-button">
{{ expandedResults[index] ? '收起' : '展开' }}
</button>
</div>
</div>
<div :class="{ expanded: expandedResults[index] }" class="result-content">
{{ text }}
<!-- <div v-if="text.length > (type === 'title' ? 20 : 900)" class="warning-badge">
超出字数限制
</div> -->
</div>
</div>
</div>
</div>
</div>
<div v-else class="empty-result-section">
<div class="empty-state">
<div class="empty-icon"></div>
<h3>等待生成结果</h3>
<p>请在左侧输入文本并点击生成按钮</p>
</div>
</div>
</transition>
</template>
<script setup>
import { ref, reactive } from 'vue'
const { results = [], isGenerating, model, layoutMode } = defineProps({
results: {
type: Array,
default: () => []
},
isGenerating: {
type: Boolean,
default: false
},
model: {
type: String,
required: true
},
layoutMode: {
type: String,
default: 'list'
}
})
defineEmits(['update:layout'])
const expandedResults = reactive({})
const copiedIndex = ref(null)
const handleCopy = (text, index) => {
navigator.clipboard
.writeText(text)
.then(() => {
copiedIndex.value = index
setTimeout(() => {
copiedIndex.value = null
}, 2000)
})
.catch((err) => {
console.error('复制失败:', err)
showToast('复制失败,请重试')
})
}
const toggleExpand = (index) => {
expandedResults[index] = !expandedResults[index]
}
const showToast = (message) => {
const toast = document.createElement('div')
toast.className = 'toast-message'
toast.textContent = message
document.body.appendChild(toast)
setTimeout(() => {
toast.classList.add('show')
setTimeout(() => {
toast.classList.remove('show')
setTimeout(() => {
document.body.removeChild(toast)
}, 300)
}, 2000)
}, 10)
}
</script>
<style scoped>
/* 右侧结果区域 */
.result-section,
.empty-result-section {
flex: 1;
background-color: white;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 28px;
display: flex;
flex-direction: column;
overflow: hidden;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.95);
}
.result-header-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.result-stats {
display: flex;
gap: 12px;
align-items: center;
}
.model-badge,
.count-badge {
padding: 6px 12px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
}
.model-badge {
background-color: #e8f5e9;
color: #2e7d32;
}
.count-badge {
background-color: #e3f2fd;
color: #1565c0;
}
.layout-toggle {
display: flex;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e0e0e0;
}
.layout-button {
background-color: white;
border: none;
padding: 6px 10px;
cursor: pointer;
transition: all 0.2s;
}
.layout-button.active {
background-color: #f0f0f0;
}
.layout-icon {
font-size: 16px;
}
.results-container {
flex: 1;
overflow-y: auto;
padding: 4px;
}
.results-list {
display: flex;
flex-direction: column;
gap: 20px;
padding: 4px;
}
.results-list.grid-layout {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.result-block {
background-color: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
overflow: hidden;
border: 1px solid #e8e8e8;
width: 100%;
transition: all 0.3s ease;
animation: fadeIn 0.5s ease forwards;
opacity: 0;
transform: translateY(20px);
display: flex;
flex-direction: column;
}
.result-block.grid-item {
height: 250px;
transition: height 0.5s ease;
}
.result-block.grid-item.expanded-result {
height: auto;
min-height: 250px;
}
.result-block:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
.result-header {
background-color: #f9f9f9;
padding: 14px 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e8e8e8;
}
.result-number {
font-weight: 600;
color: #2ecc71;
display: flex;
align-items: center;
gap: 10px;
}
.result-number::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
background-color: #2ecc71;
border-radius: 50%;
}
.result-actions {
display: flex;
gap: 8px;
}
.action-button {
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
position: relative;
min-width: 60px;
text-align: center;
}
.action-button:hover {
background-color: #f0f0f0;
border-color: #ccc;
}
.copy-button {
color: #2ecc71;
border-color: #2ecc71;
}
.copy-button:hover {
background-color: rgba(46, 204, 113, 0.1);
border-color: #2ecc71;
}
.expand-button {
color: #666;
}
.result-content {
padding: 16px;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
line-height: 1.6;
max-height: 200px;
overflow: hidden;
position: relative;
text-align: justify;
hyphens: auto;
transition: max-height 0.5s ease;
flex: 1;
}
.result-content:not(.expanded)::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 60px;
background: linear-gradient(transparent, white);
pointer-events: none;
transition: opacity 0.3s;
}
.result-content.expanded {
max-height: none;
}
/* 生成中占位符 */
.generating-placeholder {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
text-align: center;
color: #666;
}
.generating-placeholder p {
margin-top: 20px;
font-size: 16px;
}
.generating-animation {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
.dot {
width: 12px;
height: 12px;
background-color: #2ecc71;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
}
.dot:nth-child(1) {
animation-delay: -0.32s;
}
.dot:nth-child(2) {
animation-delay: -0.16s;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 40px;
color: #666;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
color: #ccc;
}
.empty-state h3 {
font-size: 20px;
margin-bottom: 8px;
color: #333;
}
.empty-state p {
font-size: 16px;
color: #666;
}
/* 标题样式 */
.section-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 16px 0;
color: #333;
display: flex;
align-items: center;
gap: 8px;
}
.section-icon {
font-size: 20px;
}
/* 字数统计 */
.char-count {
font-size: 14px;
color: #666;
font-weight: normal;
margin-left: auto;
background-color: #f0f0f0;
padding: 2px 8px;
border-radius: 12px;
}
/* 动画效果 */
@keyframes fadeIn {
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounce {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s, transform 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(20px);
}
/* 响应式设计 */
@media (max-width: 1200px) {
.result-section,
.empty-result-section {
width: 100%;
}
.results-list.grid-layout {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
@media (max-width: 768px) {
.result-section,
.empty-result-section {
padding: 20px;
border-radius: 12px;
}
}
@media (max-width: 480px) {
.result-section,
.empty-result-section {
padding: 16px;
}
.result-header {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.result-actions {
width: 100%;
justify-content: space-between;
}
.results-list.grid-layout {
display: flex;
flex-direction: column;
}
.result-block.grid-item {
height: auto;
}
}
.warning-badge {
position: absolute;
top: 8px;
right: 8px;
background-color: #ff5252;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"openai": "^4.87.4"
}
}

268
yarn.lock Normal file
View File

@ -0,0 +1,268 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node-fetch@^2.6.4":
version "2.6.12"
resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03"
integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==
dependencies:
"@types/node" "*"
form-data "^4.0.0"
"@types/node@*":
version "22.13.10"
resolved "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4"
integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==
dependencies:
undici-types "~6.20.0"
"@types/node@^18.11.18":
version "18.19.80"
resolved "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz#6d6008e8920dddcd23f9dd33da24684ef57d487c"
integrity sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==
dependencies:
undici-types "~5.26.4"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
agentkeepalive@^4.2.1:
version "4.6.0"
resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a"
integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==
dependencies:
humanize-ms "^1.2.1"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-errors "^1.3.0"
gopd "^1.2.0"
es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
dependencies:
es-errors "^1.3.0"
es-set-tostringtag@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
dependencies:
es-errors "^1.3.0"
get-intrinsic "^1.2.6"
has-tostringtag "^1.0.2"
hasown "^2.0.2"
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
form-data-encoder@1.7.2:
version "1.7.2"
resolved "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040"
integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
form-data@^4.0.0:
version "4.0.2"
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c"
integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
mime-types "^2.1.12"
formdata-node@^4.3.2:
version "4.4.1"
resolved "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2"
integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==
dependencies:
node-domexception "1.0.0"
web-streams-polyfill "4.0.0-beta.3"
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-intrinsic@^1.2.6:
version "1.3.0"
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
es-define-property "^1.0.1"
es-errors "^1.3.0"
es-object-atoms "^1.1.1"
function-bind "^1.1.2"
get-proto "^1.0.1"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
math-intrinsics "^1.1.0"
get-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
has-symbols@^1.0.3, has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
has-tostringtag@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
dependencies:
has-symbols "^1.0.3"
hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==
dependencies:
ms "^2.0.0"
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
ms@^2.0.0:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
node-domexception@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch@^2.6.7:
version "2.7.0"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
openai@^4.87.4:
version "4.87.4"
resolved "https://registry.npmjs.org/openai/-/openai-4.87.4.tgz#f9d8da366a1ded2c7aa92cb9f2256755d0e58902"
integrity sha512-lsfM20jZY4A0lNexfoUAkfmrEXxaTXvv8OKYicpeAJUNHObpRgkvC7pxPgMnB6gc9ID8OCwzzhEhBpNy69UR7w==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
abort-controller "^3.0.0"
agentkeepalive "^4.2.1"
form-data-encoder "1.7.2"
formdata-node "^4.3.2"
node-fetch "^2.6.7"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici-types@~6.20.0:
version "6.20.0"
resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
web-streams-polyfill@4.0.0-beta.3:
version "4.0.0-beta.3"
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38"
integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"