Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5efd7b0533 | ||
|
|
a80d9163b8 | ||
|
|
d25148c731 |
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
SD_API_BASE_URL=
|
||||||
|
OPENAI_API_KEY=
|
||||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Set node
|
- name: Set node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: 16.x
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Set node
|
- name: Set node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: 16.x
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [lts/*]
|
node-version: [16.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -17,10 +17,10 @@ jobs:
|
|||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
# after pnpm
|
# after pnpm
|
||||||
- name: Use Node.js LTS
|
- name: Use Node.js 16
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: 16
|
||||||
registry-url: https://registry.npmjs.org/
|
registry-url: https://registry.npmjs.org/
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
||||||
|
|||||||
35
.github/workflows/vercel.yml
vendored
35
.github/workflows/vercel.yml
vendored
@@ -1,35 +0,0 @@
|
|||||||
name: Production Tag Deployment
|
|
||||||
|
|
||||||
env:
|
|
||||||
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
|
|
||||||
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
# Pattern matched against refs/tags
|
|
||||||
tags:
|
|
||||||
- '*' # Push events to every tag not containing /
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Deploy-Production:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v2
|
|
||||||
# after pnpm
|
|
||||||
- name: Use Node.js LTS
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
registry-url: https://registry.npmjs.org/
|
|
||||||
cache: pnpm
|
|
||||||
|
|
||||||
- name: Install Vercel CLI
|
|
||||||
run: pnpm i -g vercel@latest
|
|
||||||
|
|
||||||
- name: Pull Vercel Environment Information
|
|
||||||
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
|
|
||||||
- name: Build Project Artifacts
|
|
||||||
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
|
|
||||||
- name: Deploy Project Artifacts to Vercel
|
|
||||||
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,4 +17,3 @@ dist
|
|||||||
.nuxt
|
.nuxt
|
||||||
.env
|
.env
|
||||||
.idea/
|
.idea/
|
||||||
.vercel
|
|
||||||
|
|||||||
2
.npmrc
2
.npmrc
@@ -1,5 +1,3 @@
|
|||||||
shamefully-hoist=true
|
shamefully-hoist=true
|
||||||
strict-peer-dependencies=false
|
strict-peer-dependencies=false
|
||||||
shell-emulator=true
|
shell-emulator=true
|
||||||
auto-install-peers=false
|
|
||||||
ignore-workspace-root-check=true
|
|
||||||
|
|||||||
48
.vscode/settings.json
vendored
48
.vscode/settings.json
vendored
@@ -19,6 +19,8 @@
|
|||||||
"*.css": "postcss"
|
"*.css": "postcss"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Enable the ESlint flat config support
|
||||||
|
"eslint.experimental.useFlatConfig": true,
|
||||||
// Disable the default formatter, use eslint instead
|
// Disable the default formatter, use eslint instead
|
||||||
"prettier.enable": false,
|
"prettier.enable": false,
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
@@ -29,17 +31,43 @@
|
|||||||
},
|
},
|
||||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||||
"eslint.rules.customizations": [
|
"eslint.rules.customizations": [
|
||||||
{ "rule": "style/*", "severity": "off" },
|
{
|
||||||
{ "rule": "*-indent", "severity": "off" },
|
"rule": "style/*",
|
||||||
{ "rule": "*-spacing", "severity": "off" },
|
"severity": "off"
|
||||||
{ "rule": "*-spaces", "severity": "off" },
|
},
|
||||||
{ "rule": "*-order", "severity": "off" },
|
{
|
||||||
{ "rule": "*-dangle", "severity": "off" },
|
"rule": "*-indent",
|
||||||
{ "rule": "*-newline", "severity": "off" },
|
"severity": "off"
|
||||||
{ "rule": "*quotes", "severity": "off" },
|
},
|
||||||
{ "rule": "*semi", "severity": "off" }
|
{
|
||||||
|
"rule": "*-spacing",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-spaces",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-order",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-dangle",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*-newline",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*quotes",
|
||||||
|
"severity": "off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "*semi",
|
||||||
|
"severity": "off"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// Enable eslint for all supported languages
|
// Enable eslint for all supported languages
|
||||||
"eslint.validate": [
|
"eslint.validate": [
|
||||||
"javascript",
|
"javascript",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
### 网页版本
|
### 网页版本
|
||||||
|
|
||||||
- 网站链接:[cook.yunyoujun.cn](https://cook.yunyoujun.cn)
|
- 网站链接:[cook.yunyoujun.cn](https://cook.yunyoujun.cn)
|
||||||
- 国内加速:[cook.yunle.fun](https://cook.yunle.fun)
|
|
||||||
- 备用:[cook.yyj.moe](https://cook.yyj.moe)
|
- 备用:[cook.yyj.moe](https://cook.yyj.moe)
|
||||||
- 开发版:[cook.yunle.app](https://cook.yunle.app)
|
- 开发版:[cook.yunle.app](https://cook.yunle.app)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useIndexedDB } from '~/composables/db'
|
|
||||||
import { appName } from '~/constants'
|
|
||||||
import { installPrompt } from './utils/pwa'
|
import { installPrompt } from './utils/pwa'
|
||||||
|
import { appName } from '~/constants'
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/composables/use-head
|
// https://nuxt.com/docs/api/composables/use-head
|
||||||
useHead({
|
useHead({
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<FAQItem :default-open="true" title="关于">
|
|
||||||
<div text-left>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
它诞生于 2022 年 4 月,时值疫情风控期间,希望能帮助期间的伙伴根据现有食材寻找到合适的菜谱。故原名「隔离食用手册」。
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
如今那个时期已离我们远去,故去掉「隔离」二字。但也很高兴能在这里继续与你相遇,希望它能继续发光发热,在日常生活中帮助到大家。
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="inline-flex items-center justify-center">
|
|
||||||
代码仓库:<a class="inline-flex items-center justify-center" href="https://github.com/YunYouJun/cook" target="_blank">
|
|
||||||
<div m="r-1" i-ri-github-line inline-flex />YunYouJun/cook</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="inline-flex items-center justify-center">
|
|
||||||
菜谱视频来源:
|
|
||||||
<a class="inline-flex items-center text-sm text-blue-600 dark:text-blue-400" href="https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS" target="_blank">
|
|
||||||
<div m="r-1" i-ri-bilibili-line inline-flex />
|
|
||||||
<span class="inline-flex">隔离食用手册大全</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</FAQItem>
|
|
||||||
</template>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="tagSize pureTag rounded"
|
|
||||||
p="x-2"
|
|
||||||
border="~ yellow-200 dark:yellow-800"
|
|
||||||
bg="yellow-300 opacity-20"
|
|
||||||
text="yellow-800 dark:yellow-200"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,141 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="w-full">
|
|
||||||
<CommonHeader>
|
|
||||||
帮助
|
|
||||||
</CommonHeader>
|
|
||||||
|
|
||||||
<InstallPwa />
|
|
||||||
|
|
||||||
<FeedbackActions />
|
|
||||||
|
|
||||||
<div class="mx-auto max-w-md w-full rounded-2xl p-2" text-left>
|
|
||||||
<FAQItem title="未来计划?">
|
|
||||||
计划增加新功能,如自定义菜谱,与使用其他用户分享的菜谱。
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<FAQItem title="什么是模式?">
|
|
||||||
<ul>
|
|
||||||
<li><b>模糊匹配</b>:展示所有含当前选中任意食材的菜谱</li>
|
|
||||||
<li><b>精准匹配</b>:展示所有含当前选中所有食材的菜谱</li>
|
|
||||||
<li><b>生存模式</b>:展示当前选中食材即可制作的所有菜谱</li>
|
|
||||||
</ul>
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<FAQItem title="如何快速清空所选食材和工具?">
|
|
||||||
<div inline-flex items-center justify-center>
|
|
||||||
点击顶部 <div i-mdi-pot-steam-outline mx-1 inline-block /> 图标即可。
|
|
||||||
</div>
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<FAQItem title="是否有微信小程序?">
|
|
||||||
因不可抗力(小程序因跳转 B 站视频而被判定为导流违规)下架。
|
|
||||||
将不再提供小程序版本。
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
搜索微信公众号<b>「云游君」</b>并发送<b>「做菜」</b>,也可以快速找到本网站。
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<FAQItem title="是否有 APP?">
|
|
||||||
<b>暂时没有开发 APP 的计划。</b>
|
|
||||||
<br>
|
|
||||||
但我们正在优化 <b>PWA</b> 的体验,以便您可以直接将本站添加到桌面,并享受<b>类似 APP 的体验</b>。
|
|
||||||
<br>
|
|
||||||
你可以使用浏览器打开,点击上方的<b>「安装到桌面」</b>或在菜单中点击<b>「添加到主屏幕」</b>。
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<FAQItem title="未来是否会收费?">
|
|
||||||
该项目将以免费开源的形式运营。
|
|
||||||
<br>
|
|
||||||
您可以考虑赞助本项目,以支持我们的开发。
|
|
||||||
我会将其投入在周边的服务器、域名、CDN 等费用上。
|
|
||||||
<ul mt-1>
|
|
||||||
<li>
|
|
||||||
<a href="https://afdian.net/a/yunyoujun" target="_blank">爱发电赞助</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://sponsors.yunyoujun.cn/" target="_blank">我要直接打钱!</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<FAQItem title="页面无法点击、资源加载失败?">
|
|
||||||
<blockquote>
|
|
||||||
试试「无痕模式」是否正常?
|
|
||||||
</blockquote>
|
|
||||||
<br>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<b>清除 Cookie</b>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
点击浏览器网址前方的 🔒 图标
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
点击「Cookie」并清除
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<b>强制刷新缓存</b>
|
|
||||||
<ul>
|
|
||||||
<li>Windows: <code>Ctrl + F5</code></li>
|
|
||||||
<li>macOS: <code>Cmd + Shift + R</code></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<hr h="1" my="4" bg-black>
|
|
||||||
|
|
||||||
<HelpAbout />
|
|
||||||
|
|
||||||
<FAQItem title="关于我">
|
|
||||||
<div text-left>
|
|
||||||
我的个人微信公众号「云游君」,会分享一些生活和写的<a href="https://sponsors.yunyoujun.cn/projects" target="_blank">
|
|
||||||
小玩具们
|
|
||||||
</a>。
|
|
||||||
|
|
||||||
<a inline-flex py-4 href="https://cdn.yunyoujun.cn/img/about/white-qrcode-and-search.jpg" target="_blank">
|
|
||||||
<img src="https://cdn.yunyoujun.cn/img/about/white-qrcode-and-search.jpg">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<AboutMe />
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<FAQItem title="致谢">
|
|
||||||
<p>
|
|
||||||
感谢以下小伙伴为本项目提供的数据支持和 QA !
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ul mt-2 text-left text-sm>
|
|
||||||
<li>
|
|
||||||
<a href="https://weibo.com/runny" target="_blank">Runny</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
麒麟
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
晴方啾
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
课代表阿伟
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</FAQItem>
|
|
||||||
|
|
||||||
<FAQItem title="赞助者们">
|
|
||||||
<div>
|
|
||||||
感谢至今以来所有的<a href="https://afdian.net/a/yunyoujun" class="text-purple" target="_blank">赞助者</a>们,你们的支持是我持续维护和开发新项目的动力!
|
|
||||||
</div>
|
|
||||||
<div pt-2>
|
|
||||||
<a href="https://sponsors.yunyoujun.cn" target="_blank">
|
|
||||||
<img src="https://sponsors.yunyoujun.cn/sponsors.svg">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</FAQItem>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<BaseFooter mt-4 />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
const rStore = useRecipeStore()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div text-4xl>
|
|
||||||
<button
|
|
||||||
class="cursor-pointer transition active:text-green-800 hover:(text-green-600)"
|
|
||||||
title="重置"
|
|
||||||
@click="rStore.reset"
|
|
||||||
>
|
|
||||||
<div v-if="rStore.selectedStuff.length" i-mdi-pot-steam-outline />
|
|
||||||
<div v-else i-mdi-pot-mix-outline />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p text="sm" m="b-4">
|
|
||||||
好的,今天我们来做菜!
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<ChooseFood />
|
|
||||||
<SimpleCopyright />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -8,10 +8,7 @@ const props = defineProps({
|
|||||||
const rStore = useRecipeStore()
|
const rStore = useRecipeStore()
|
||||||
const { displayedRecipe } = storeToRefs(rStore)
|
const { displayedRecipe } = storeToRefs(rStore)
|
||||||
|
|
||||||
/**
|
const showBasketBtn = computed(async () => {
|
||||||
* Show basket button if there are recipes in the basket
|
|
||||||
*/
|
|
||||||
const showBasketBtn = computed(() => {
|
|
||||||
return displayedRecipe.value.length !== rStore.recipesLength && props.isVisible
|
return displayedRecipe.value.length !== rStore.recipesLength && props.isVisible
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { StuffItem } from '~/types'
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useEmojiAnimation } from '~/composables/animation'
|
import type { StuffItem } from '~/types'
|
||||||
|
|
||||||
import { meat, staple, tools, vegetable } from '~/data/food'
|
import { meat, staple, tools, vegetable } from '~/data/food'
|
||||||
|
|
||||||
|
import { useEmojiAnimation } from '~/composables/animation'
|
||||||
|
|
||||||
const rStore = useRecipeStore()
|
const rStore = useRecipeStore()
|
||||||
const { curTool } = storeToRefs(rStore)
|
const { curTool } = storeToRefs(rStore)
|
||||||
const curStuff = computed(() => rStore.selectedStuff)
|
const curStuff = computed(() => rStore.selectedStuff)
|
||||||
178
components/ai/AiChooseFood.vue
Normal file
178
components/ai/AiChooseFood.vue
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { StuffItem } from '~/types'
|
||||||
|
import { meat, staple, vegetable } from '~/data/food'
|
||||||
|
|
||||||
|
import { useEmojiAnimation } from '~/composables/animation'
|
||||||
|
import type { AIRecipeInfo } from '~/packages/ai/src'
|
||||||
|
import { generateRecipeInfo, getRecipeImage } from '~/utils/api'
|
||||||
|
|
||||||
|
const rStore = useRecipeStore()
|
||||||
|
const curStuff = computed(() => rStore.selectedStuff)
|
||||||
|
|
||||||
|
const recipeBtnRef = ref<HTMLButtonElement>()
|
||||||
|
const { playAnimation } = useEmojiAnimation(recipeBtnRef)
|
||||||
|
|
||||||
|
const gtm = useGtm()
|
||||||
|
const recipePanelRef = ref()
|
||||||
|
const { isVisible, show } = useInvisibleElement(recipePanelRef)
|
||||||
|
|
||||||
|
function toggleStuff(item: StuffItem, category = '', _e?: Event) {
|
||||||
|
rStore.toggleStuff(item.name)
|
||||||
|
|
||||||
|
if (curStuff.value.includes(item.name))
|
||||||
|
playAnimation(item.emoji)
|
||||||
|
|
||||||
|
gtm?.trackEvent({
|
||||||
|
event: 'click',
|
||||||
|
category: `${category}_${item.name}`,
|
||||||
|
action: 'click_stuff',
|
||||||
|
label: '食材',
|
||||||
|
})
|
||||||
|
gtm?.trackEvent({
|
||||||
|
event: 'click_stuff',
|
||||||
|
action: item.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// cook recipe
|
||||||
|
const cooking = ref(false)
|
||||||
|
const recipeImg = ref('')
|
||||||
|
|
||||||
|
const aiRecipeInfo = ref<AIRecipeInfo>({
|
||||||
|
名称: '名称',
|
||||||
|
介绍: '介绍',
|
||||||
|
})
|
||||||
|
|
||||||
|
async function cook() {
|
||||||
|
cooking.value = true
|
||||||
|
const foods = rStore.selectedStuff
|
||||||
|
|
||||||
|
// reset
|
||||||
|
aiRecipeInfo.value = ({
|
||||||
|
名称: '起名中...',
|
||||||
|
介绍: '正在思考怎么介绍...',
|
||||||
|
})
|
||||||
|
recipeImg.value = ''
|
||||||
|
|
||||||
|
// generate
|
||||||
|
const [info, img] = await Promise.all([generateRecipeInfo(foods), getRecipeImage(foods)])
|
||||||
|
aiRecipeInfo.value = info
|
||||||
|
recipeImg.value = img
|
||||||
|
|
||||||
|
cooking.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2 m="t-4" text="xl" font="bold" p="1">
|
||||||
|
🥘 先选一下食材
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<h2 opacity="90" text="base" font="bold" p="1">
|
||||||
|
🥬 菜菜们
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<VegetableTag
|
||||||
|
v-for="item, i in vegetable" :key="i"
|
||||||
|
:active="curStuff.includes(item.name)"
|
||||||
|
@click="toggleStuff(item, 'vegetable')"
|
||||||
|
>
|
||||||
|
<span v-if="item.emoji" class="inline-flex">{{ item.emoji }}</span>
|
||||||
|
<span v-else-if="item.image" class="inline-flex">
|
||||||
|
<img class="inline-flex" w="2" h="2" width="10" height="10" :src="item.image" :alt="item.name">
|
||||||
|
</span>
|
||||||
|
<span class="inline-flex" m="l-1">{{ item.name }}</span>
|
||||||
|
</VegetableTag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div m="y-4">
|
||||||
|
<h2 opacity="90" text="base" font="bold" p="1">
|
||||||
|
🥩 肉肉们
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<MeatTag
|
||||||
|
v-for="item, i in meat" :key="i"
|
||||||
|
:active="curStuff.includes(item.name)"
|
||||||
|
@click="toggleStuff(item, 'meat')"
|
||||||
|
>
|
||||||
|
<span>{{ item.emoji }}</span>
|
||||||
|
<span m="l-1">{{ item.name }}</span>
|
||||||
|
</MeatTag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div m="y-4">
|
||||||
|
<h2 opacity="90" text="base" font="bold" p="1">
|
||||||
|
🍚 主食
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<StapleTag
|
||||||
|
v-for="item, i in staple" :key="i"
|
||||||
|
:active="curStuff.includes(item.name)"
|
||||||
|
@click="toggleStuff(item, 'staple')"
|
||||||
|
>
|
||||||
|
<span>{{ item.emoji }}</span>
|
||||||
|
<span m="l-1">{{ item.name }}</span>
|
||||||
|
</StapleTag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div m="t-4">
|
||||||
|
<h2 text="xl" font="bold" p="1">
|
||||||
|
🍳 再选一下厨具
|
||||||
|
</h2>
|
||||||
|
<div>
|
||||||
|
<ToolTag
|
||||||
|
v-for="item, i in tools" :key="i"
|
||||||
|
:active="curTool === item.name"
|
||||||
|
@click="rStore.clickTool(item)"
|
||||||
|
>
|
||||||
|
<span v-if="item.emoji" class="inline-flex">
|
||||||
|
{{ item.emoji }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="item.icon" class="inline-flex">
|
||||||
|
<div :class="item.icon" />
|
||||||
|
</span>
|
||||||
|
<span class="inline-flex" m="l-1">{{ item.label || item.name }}</span>
|
||||||
|
</ToolTag>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<Transition>
|
||||||
|
<BasketButton ref="recipeBtnRef" :is-visible="isVisible" @click="show" />
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<button
|
||||||
|
m-auto
|
||||||
|
flex items-center justify-center
|
||||||
|
class="rounded bg-yellow px-4 py-2 text-orange-900 font-black shadow hover:shadow-md active:shadow-inset"
|
||||||
|
@click="cook()"
|
||||||
|
>
|
||||||
|
<div v-if="cooking" class="mr-2 inline-flex" i-svg-spinners:clock />
|
||||||
|
<span>做黑暗料理 🥘</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="recipe-panel relative shadow transition"
|
||||||
|
m="x-2 y-4" p="2"
|
||||||
|
bg="gray-400/8"
|
||||||
|
>
|
||||||
|
<div text="xl" font="bold" p="1">
|
||||||
|
「{{ aiRecipeInfo['名称'] }}」
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cook-recipes text-center" p="2">
|
||||||
|
<img
|
||||||
|
v-if="recipeImg"
|
||||||
|
class="m-auto w-25 rounded shadow transition hover:shadow-md"
|
||||||
|
:src="recipeImg"
|
||||||
|
alt="recipes"
|
||||||
|
>
|
||||||
|
<div v-else class="m-auto h-25 w-25 rounded bg-gray shadow transition hover:shadow-md" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ aiRecipeInfo['介绍'] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -25,7 +25,7 @@ const filteredRecipes = computedAsync(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<YlfIconButton
|
<YlfIconButton
|
||||||
absolute right-4 top-4
|
absolute right-3 top-4
|
||||||
class="icon-btn hover:text-yellow-400 !outline-none"
|
class="icon-btn hover:text-yellow-400 !outline-none"
|
||||||
text-xl
|
text-xl
|
||||||
title="切换" @click="openModal"
|
title="切换" @click="openModal"
|
||||||
@@ -89,9 +89,9 @@ const filteredRecipes = computedAsync(async () => {
|
|||||||
@click="keyword = ''"
|
@click="keyword = ''"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button op="70" ml-2 inline-flex cursor-pointer text-base @click="closeModal">
|
<div op="70" ml-2 inline-flex cursor-pointer text-base @click="closeModal">
|
||||||
取消
|
取消
|
||||||
</button>
|
</div>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<div flex="~ col grow" overflow="auto" class="mt-2" text-xs>
|
<div flex="~ col grow" overflow="auto" class="mt-2" text-xs>
|
||||||
<DishTag v-for="item, i in filteredRecipes" :key="i" :dish="item" />
|
<DishTag v-for="item, i in filteredRecipes" :key="i" :dish="item" />
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VueAboutMe } from 'vue-about-me'
|
import VueAboutMe from 'vue-about-me'
|
||||||
import 'vue-about-me/style.css'
|
import 'vue-about-me/style.css'
|
||||||
|
|
||||||
const color = useColorMode()
|
const color = useColorMode()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import pkg from '~/../package.json'
|
import pkg from '~/package.json'
|
||||||
|
|
||||||
const commitSha = (import.meta.env.VITE_COMMIT_REF || '').slice(0, 7)
|
const commitSha = (import.meta.env.VITE_COMMIT_REF || '').slice(0, 7)
|
||||||
const now = import.meta.env.VITE_APP_BUILD_TIME
|
const now = import.meta.env.VITE_APP_BUILD_TIME
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { RecipeItem } from '~/types'
|
|
||||||
import type { DbRecipeItem } from '~/utils/db'
|
import type { DbRecipeItem } from '~/utils/db'
|
||||||
import { recipeHistories } from '~/composables/store/history'
|
|
||||||
import { tools } from '~/data/food'
|
import { tools } from '~/data/food'
|
||||||
|
import type { RecipeItem } from '~/types'
|
||||||
import { getEmojisFromStuff } from '~/utils'
|
import { getEmojisFromStuff } from '~/utils'
|
||||||
|
import { recipeHistories } from '~/composables/store/history'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
dish: RecipeItem | DbRecipeItem
|
dish: RecipeItem | DbRecipeItem
|
||||||
@@ -5,12 +5,12 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<span
|
||||||
class="meat-tag rounded tag" p="x-2"
|
class="meat-tag rounded tag" p="x-2"
|
||||||
border="~ red-200 dark:red-800"
|
border="~ red-200 dark:red-800"
|
||||||
:bg="active ? 'red-500 opacity-90' : 'red-300 opacity-20'"
|
:bg="active ? 'red-500 opacity-90' : 'red-300 opacity-20'"
|
||||||
:text="active ? 'red-100' : 'red-800 dark:red-200'"
|
:text="active ? 'red-100' : 'red-800 dark:red-200'"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -5,11 +5,11 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<span
|
||||||
class="rounded tag" p="x-2" border="~ yellow-200 dark:yellow-800"
|
class="rounded tag" p="x-2" border="~ yellow-200 dark:yellow-800"
|
||||||
:bg="active ? 'yellow-500 dark:yellow-600 opacity-100' : 'yellow-300 opacity-20'"
|
:bg="active ? 'yellow-500 dark:yellow-600 opacity-100' : 'yellow-300 opacity-20'"
|
||||||
:text="active ? 'yellow-100' : 'yellow-800 dark:yellow-200'"
|
:text="active ? 'yellow-100' : 'yellow-800 dark:yellow-200'"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -5,12 +5,12 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<span
|
||||||
class="rounded tag" p="x-2"
|
class="rounded tag" p="x-2"
|
||||||
border="~ stone-200 dark:stone-600"
|
border="~ stone-200 dark:stone-600"
|
||||||
:bg="active ? 'stone-600 opacity-100' : 'stone-300 opacity-5'"
|
:bg="active ? 'stone-600 opacity-100' : 'stone-300 opacity-5'"
|
||||||
:text="active ? 'stone-100' : 'stone-800 dark:stone-200'"
|
:text="active ? 'stone-100' : 'stone-800 dark:stone-200'"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -5,12 +5,12 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<span
|
||||||
class="vegetable-tag rounded tag" p="x-2"
|
class="vegetable-tag rounded tag" p="x-2"
|
||||||
border="~ green-200 dark:green-800"
|
border="~ green-200 dark:green-800"
|
||||||
:bg="active ? 'green-600 opacity-90' : 'green-300 opacity-20'"
|
:bg="active ? 'green-600 opacity-90' : 'green-300 opacity-20'"
|
||||||
:text="active ? 'green-100' : 'green-800 dark:green-200'"
|
:text="active ? 'green-100' : 'green-800 dark:green-200'"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Ref } from 'vue'
|
|
||||||
import { isClient } from '@vueuse/core'
|
import { isClient } from '@vueuse/core'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
export function useEmojiAnimation(recipeBtn: Ref<HTMLButtonElement | undefined>) {
|
export function useEmojiAnimation(recipeBtn: Ref<HTMLButtonElement | undefined>) {
|
||||||
const { x, y } = usePointer()
|
const { x, y } = usePointer()
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { lastDbUpdated, namespace } from '~/constants'
|
import { lastDbUpdated, namespace } from '~/constants'
|
||||||
import { db, initDb } from '~/utils/db'
|
|
||||||
|
|
||||||
export function useIndexedDB() {
|
export function useIndexedDB() {
|
||||||
const dbUpdated = useStorage(`${namespace}:lastDbUpdated`, lastDbUpdated)
|
const dbUpdated = useStorage(`${namespace}:lastDbUpdated`, lastDbUpdated)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { DbRecipeItem } from '~/utils/db'
|
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { namespace } from '~/constants'
|
import { namespace } from '~/constants'
|
||||||
|
import type { DbRecipeItem } from '~/utils/db'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 随机几道菜
|
* 随机几道菜
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { namespace } from '../../constants'
|
|
||||||
import { defaultSettings } from '../../utils/settings'
|
import { defaultSettings } from '../../utils/settings'
|
||||||
|
import { namespace } from '../../constants'
|
||||||
|
|
||||||
export const useAppStore = defineStore('app', () => {
|
export const useAppStore = defineStore('app', () => {
|
||||||
const deferredPrompt = ref<Event | any>()
|
const deferredPrompt = ref<Event | any>()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { RecipeItem } from '~/types'
|
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { namespace } from '~/constants'
|
import { namespace } from '~/constants'
|
||||||
|
import type { RecipeItem } from '~/types'
|
||||||
|
|
||||||
export interface RecipeHistoryItem {
|
export interface RecipeHistoryItem {
|
||||||
recipe: RecipeItem
|
recipe: RecipeItem
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { RecipeItem, StuffItem } from '~/types'
|
|
||||||
import { useGtm } from '@gtm-support/vue-gtm'
|
|
||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
import { useGtm } from '@gtm-support/vue-gtm'
|
||||||
import { db } from '../../utils/db'
|
import { db } from '../../utils/db'
|
||||||
import { useAppStore } from './app'
|
import { useAppStore } from './app'
|
||||||
|
import type { RecipeItem, StuffItem } from '~/types'
|
||||||
|
|
||||||
const namespace = 'cook'
|
const namespace = 'cook'
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ModuleOptions } from '@vite-pwa/nuxt'
|
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
|
import type { ModuleOptions } from '@vite-pwa/nuxt'
|
||||||
import { appDescription, appName } from '../constants/index'
|
import { appDescription, appName } from '../constants/index'
|
||||||
|
|
||||||
const scope = '/'
|
const scope = '/'
|
||||||
@@ -7,63 +7,78 @@ export const vegetable: StuffItem[] = [
|
|||||||
{
|
{
|
||||||
name: '土豆',
|
name: '土豆',
|
||||||
emoji: '🥔',
|
emoji: '🥔',
|
||||||
|
en: 'potato',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '胡萝卜',
|
name: '胡萝卜',
|
||||||
emoji: '🥕',
|
emoji: '🥕',
|
||||||
|
en: 'carrot',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '花菜',
|
name: '花菜',
|
||||||
emoji: '🥦',
|
emoji: '🥦',
|
||||||
|
en: 'cauliflower',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '白萝卜',
|
name: '白萝卜',
|
||||||
emoji: '🥣',
|
emoji: '🥣',
|
||||||
|
en: 'radish',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '西葫芦',
|
name: '西葫芦',
|
||||||
emoji: '🥒',
|
emoji: '🥒',
|
||||||
|
en: 'zucchini',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '番茄',
|
name: '番茄',
|
||||||
emoji: '🍅',
|
emoji: '🍅',
|
||||||
alias: '西红柿',
|
alias: '西红柿',
|
||||||
|
en: 'tomato',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '芹菜',
|
name: '芹菜',
|
||||||
emoji: '🥬',
|
emoji: '🥬',
|
||||||
|
en: 'celery',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '黄瓜',
|
name: '黄瓜',
|
||||||
emoji: '🥒',
|
emoji: '🥒',
|
||||||
|
en: 'cucumber',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '洋葱',
|
name: '洋葱',
|
||||||
emoji: '🧅',
|
emoji: '🧅',
|
||||||
|
en: 'onion',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '莴笋',
|
name: '莴笋',
|
||||||
emoji: '🎍',
|
emoji: '🎍',
|
||||||
|
en: 'lettuce',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '菌菇',
|
name: '菌菇',
|
||||||
emoji: '🍄',
|
emoji: '🍄',
|
||||||
|
en: 'mushroom',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '茄子',
|
name: '茄子',
|
||||||
emoji: '🍆',
|
emoji: '🍆',
|
||||||
|
en: 'eggplant',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '豆腐',
|
name: '豆腐',
|
||||||
emoji: '🍲',
|
emoji: '🍲',
|
||||||
|
en: 'tofu',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '包菜',
|
name: '包菜',
|
||||||
emoji: '🥗',
|
emoji: '🥗',
|
||||||
|
en: 'cabbage',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '白菜',
|
name: '白菜',
|
||||||
emoji: '🥬',
|
emoji: '🥬',
|
||||||
|
en: 'cabbage',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -74,42 +89,52 @@ export const meat: StuffItem[] = [
|
|||||||
{
|
{
|
||||||
name: '午餐肉',
|
name: '午餐肉',
|
||||||
emoji: '🥓',
|
emoji: '🥓',
|
||||||
|
en: 'bacon',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '香肠',
|
name: '香肠',
|
||||||
emoji: '🌭',
|
emoji: '🌭',
|
||||||
|
en: 'sausage',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '腊肠',
|
name: '腊肠',
|
||||||
emoji: '🌭',
|
emoji: '🌭',
|
||||||
|
en: 'sausage',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '鸡肉',
|
name: '鸡肉',
|
||||||
emoji: '🐤',
|
emoji: '🐤',
|
||||||
|
en: 'chicken',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '猪肉',
|
name: '猪肉',
|
||||||
emoji: '🐷',
|
emoji: '🐷',
|
||||||
|
en: 'pork',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '鸡蛋',
|
name: '鸡蛋',
|
||||||
emoji: '🥚',
|
emoji: '🥚',
|
||||||
|
en: 'egg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '虾',
|
name: '虾',
|
||||||
emoji: '🦐',
|
emoji: '🦐',
|
||||||
|
en: 'shrimp',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '牛肉',
|
name: '牛肉',
|
||||||
emoji: '🐮',
|
emoji: '🐮',
|
||||||
|
en: 'beef',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '骨头',
|
name: '骨头',
|
||||||
emoji: '🦴',
|
emoji: '🦴',
|
||||||
|
en: 'bone',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '鱼(Todo)',
|
name: '鱼(Todo)',
|
||||||
emoji: '🐟',
|
emoji: '🐟',
|
||||||
|
en: 'fish',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -120,18 +145,22 @@ export const staple: StuffItem[] = [
|
|||||||
{
|
{
|
||||||
name: '面食',
|
name: '面食',
|
||||||
emoji: '🍝',
|
emoji: '🍝',
|
||||||
|
en: 'noodles',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '面包',
|
name: '面包',
|
||||||
emoji: '🍞',
|
emoji: '🍞',
|
||||||
|
en: 'bread',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '米',
|
name: '米',
|
||||||
emoji: '🍚',
|
emoji: '🍚',
|
||||||
|
en: 'rice',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '方便面',
|
name: '方便面',
|
||||||
emoji: '🍜',
|
emoji: '🍜',
|
||||||
|
en: 'instant noodles',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,8 @@
|
|||||||
// @ts-check
|
|
||||||
import antfu from '@antfu/eslint-config'
|
import antfu from '@antfu/eslint-config'
|
||||||
import nuxt from './.nuxt/eslint.config.mjs'
|
|
||||||
|
|
||||||
export default nuxt(
|
export default antfu(
|
||||||
antfu(
|
{
|
||||||
{
|
unocss: true,
|
||||||
unocss: true,
|
formatters: true,
|
||||||
formatters: true,
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
ignores: [
|
|
||||||
'app/data/*.json',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="cook-main text-center text-gray-700 dark:text-gray-200" p="t-8 b-$cook-bottom-menu-height">
|
<main class="cook-main text-center text-gray-700 dark:text-gray-200" p="t-8 b-$cook-bottom-menu-height">
|
||||||
<slot />
|
<slot />
|
||||||
<DarkToggle absolute left-4 top-4 />
|
<DarkToggle absolute left-3 top-5 />
|
||||||
<SearchRecipe />
|
<SearchRecipe />
|
||||||
<TheBottomMenu fixed bottom-0 left-0 right-0 />
|
<TheBottomMenu fixed bottom-0 left-0 right-0 />
|
||||||
</main>
|
</main>
|
||||||
6
layouts/preview.vue
Normal file
6
layouts/preview.vue
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<main class="cook-main text-center text-gray-700 dark:text-gray-200" p="t-8 b-$cook-bottom-menu-height">
|
||||||
|
<slot />
|
||||||
|
<DarkToggle absolute left-3 top-4 />
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
import { pwa } from './app/config/pwa'
|
import { pwa } from './config/pwa'
|
||||||
import { appDescription } from './app/constants/index'
|
import { appDescription } from './constants/index'
|
||||||
|
|
||||||
Object.assign(process.env, {
|
Object.assign(process.env, {
|
||||||
VITE_COMMIT_REF: process.env.CF_PAGES_COMMIT_SHA || '',
|
VITE_COMMIT_REF: process.env.CF_PAGES_COMMIT_SHA || '',
|
||||||
@@ -9,50 +9,29 @@ Object.assign(process.env, {
|
|||||||
// add build time to env
|
// add build time to env
|
||||||
import.meta.env.VITE_APP_BUILD_TIME = new Date().getTime().toString()
|
import.meta.env.VITE_APP_BUILD_TIME = new Date().getTime().toString()
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
|
ssr: false,
|
||||||
|
|
||||||
modules: [
|
modules: [
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
'@unocss/nuxt',
|
'@unocss/nuxt',
|
||||||
'@pinia/nuxt',
|
'@pinia/nuxt',
|
||||||
|
|
||||||
|
'@nuxt/test-utils/module',
|
||||||
'@nuxtjs/color-mode',
|
'@nuxtjs/color-mode',
|
||||||
'@vite-pwa/nuxt',
|
'@vite-pwa/nuxt',
|
||||||
'@nuxt/eslint',
|
|
||||||
'@nuxt/test-utils/module',
|
|
||||||
|
|
||||||
'@zadigetvoltaire/nuxt-gtm',
|
'@zadigetvoltaire/nuxt-gtm',
|
||||||
|
|
||||||
'@yunlefun/vue/nuxt',
|
'@yunlefun/vue/nuxt',
|
||||||
|
|
||||||
// fix QQ in iOS, Done
|
|
||||||
// See https://github.com/unjs/ofetch/pull/366
|
|
||||||
// 'nuxt-fix-ofetch',
|
|
||||||
],
|
|
||||||
ssr: false,
|
|
||||||
|
|
||||||
components: [
|
|
||||||
{ path: '~/components', pathPrefix: false },
|
|
||||||
],
|
],
|
||||||
|
|
||||||
devtools: {
|
experimental: {
|
||||||
enabled: true,
|
// when using generate, payload js assets included in sw precache manifest
|
||||||
},
|
// but missing on offline, disabling extraction it until fixed
|
||||||
|
payloadExtraction: false,
|
||||||
app: {
|
// inlineSSRStyles: false,
|
||||||
head: {
|
renderJsonPayloads: true,
|
||||||
viewport: 'width=device-width,initial-scale=1',
|
typedPages: true,
|
||||||
link: [
|
|
||||||
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
|
|
||||||
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
|
|
||||||
],
|
|
||||||
meta: [
|
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no' },
|
|
||||||
{ name: 'description', content: appDescription },
|
|
||||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
|
||||||
{ name: 'theme-color', media: '(prefers-color-scheme: light)', content: 'white' },
|
|
||||||
{ name: 'theme-color', media: '(prefers-color-scheme: dark)', content: '#222222' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
css: [
|
css: [
|
||||||
@@ -65,25 +44,6 @@ export default defineNuxtConfig({
|
|||||||
classSuffix: '',
|
classSuffix: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
future: {
|
|
||||||
compatibilityVersion: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
features: {
|
|
||||||
// For UnoCSS
|
|
||||||
inlineStyles: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
experimental: {
|
|
||||||
// when using generate, payload js assets included in sw precache manifest
|
|
||||||
// but missing on offline, disabling extraction it until fixed
|
|
||||||
payloadExtraction: false,
|
|
||||||
renderJsonPayloads: true,
|
|
||||||
typedPages: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
compatibilityDate: '2024-08-14',
|
|
||||||
|
|
||||||
nitro: {
|
nitro: {
|
||||||
esbuild: {
|
esbuild: {
|
||||||
options: {
|
options: {
|
||||||
@@ -97,18 +57,32 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
eslint: {
|
app: {
|
||||||
config: {
|
head: {
|
||||||
standalone: false,
|
viewport: 'width=device-width,initial-scale=1',
|
||||||
nuxt: {
|
link: [
|
||||||
sortConfigKeys: true,
|
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' },
|
||||||
},
|
{ rel: 'apple-touch-icon', href: '/apple-touch-icon.png' },
|
||||||
|
],
|
||||||
|
meta: [
|
||||||
|
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no' },
|
||||||
|
{ name: 'description', content: appDescription },
|
||||||
|
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
components: [
|
||||||
|
{ path: '~/components', pathPrefix: false },
|
||||||
|
],
|
||||||
|
|
||||||
gtm: {
|
gtm: {
|
||||||
id: 'GTM-5FJSV46',
|
id: 'GTM-5FJSV46',
|
||||||
},
|
},
|
||||||
|
|
||||||
pwa,
|
pwa,
|
||||||
|
|
||||||
|
devtools: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
99
package.json
99
package.json
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.2.4",
|
"version": "1.2.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.15.4",
|
"packageManager": "pnpm@8.15.5",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run convert && nuxt build",
|
"build": "npm run convert && nuxt build",
|
||||||
"build:static": "npm run convert && nuxt generate",
|
"build:static": "npm run convert && nuxt generate",
|
||||||
"convert": "pnpm -C scripts run convert",
|
"convert": "tsx scripts/convert.ts",
|
||||||
"dev": "nuxt dev --host",
|
"dev": "nuxt dev --host",
|
||||||
"dev:pwa": "VITE_PLUGIN_PWA=true nuxi dev",
|
"dev:pwa": "VITE_PLUGIN_PWA=true nuxi dev",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
@@ -24,61 +24,50 @@
|
|||||||
"typecheck": "vue-tsc --noEmit"
|
"typecheck": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.10",
|
||||||
"vue-about-me": "^1.4.0"
|
"vue-about-me": "^1.2.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^3.16.0",
|
"@antfu/eslint-config": "^2.9.0",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.19",
|
||||||
"@iconify-json/carbon": "^1.2.5",
|
"@iconify-json/carbon": "^1.1.31",
|
||||||
"@iconify-json/fe": "^1.2.2",
|
"@iconify-json/fe": "^1.1.10",
|
||||||
"@iconify-json/gg": "^1.2.2",
|
"@iconify-json/gg": "^1.1.9",
|
||||||
"@iconify-json/ic": "^1.2.2",
|
"@iconify-json/ic": "^1.1.17",
|
||||||
"@iconify-json/mdi": "^1.2.3",
|
"@iconify-json/mdi": "^1.1.64",
|
||||||
"@iconify-json/ri": "^1.2.5",
|
"@iconify-json/ri": "^1.1.20",
|
||||||
"@iconify-json/twemoji": "^1.2.2",
|
"@iconify-json/svg-spinners": "^1.1.2",
|
||||||
"@nuxt/devtools": "^1.7.0",
|
"@iconify-json/twemoji": "^1.1.15",
|
||||||
"@nuxt/eslint": "^0.7.5",
|
"@nuxt/devtools": "^1.1.3",
|
||||||
"@nuxt/test-utils": "^3.15.4",
|
"@nuxt/test-utils": "^3.12.0",
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@nuxtjs/color-mode": "^3.3.3",
|
||||||
"@pinia/nuxt": "^0.9.0",
|
"@pinia/nuxt": "^0.5.1",
|
||||||
"@pinia/testing": "^0.1.7",
|
"@pinia/testing": "^0.1.3",
|
||||||
"@unocss/eslint-config": "^65.4.3",
|
"@unocss/eslint-config": "^0.58.6",
|
||||||
"@unocss/nuxt": "^65.4.3",
|
"@unocss/nuxt": "^0.58.6",
|
||||||
"@vite-pwa/nuxt": "^0.10.6",
|
"@vite-pwa/nuxt": "^0.6.0",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.5",
|
||||||
"@vueuse/nuxt": "^12.5.0",
|
"@vueuse/nuxt": "^10.9.0",
|
||||||
"@yunlefun/vue": "^0.1.1",
|
"@yunlefun/vue": "^0.1.1",
|
||||||
"@zadigetvoltaire/nuxt-gtm": "^0.0.13",
|
"@zadigetvoltaire/nuxt-gtm": "^0.0.13",
|
||||||
"bumpp": "^9.10.2",
|
"bumpp": "^9.4.0",
|
||||||
"consola": "^3.4.0",
|
"consola": "^3.2.3",
|
||||||
"dexie": "^4.0.11",
|
"dexie": "^3.2.7",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-format": "^1.0.1",
|
"eslint-plugin-format": "^0.1.0",
|
||||||
"fake-indexeddb": "^6.0.0",
|
"fake-indexeddb": "^5.0.2",
|
||||||
"jsdom": "^26.0.0",
|
"happy-dom": "^14.3.1",
|
||||||
"lint-staged": "^15.4.2",
|
"jsdom": "^24.0.0",
|
||||||
"nuxt": "^3.15.2",
|
"nuxt": "^3.9.3",
|
||||||
"pinia": "^2.3.1",
|
"nuxt-vitest": "^0.11.5",
|
||||||
"sass": "^1.83.4",
|
"pinia": "^2.1.7",
|
||||||
"serve": "^14.2.4",
|
"sass": "^1.72.0",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"serve": "^14.2.1",
|
||||||
"star-markdown-css": "^0.5.3",
|
"star-markdown-css": "^0.4.2",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.7.1",
|
||||||
"typescript": "5.7.2",
|
"typescript": "^5.4.3",
|
||||||
"unocss": "^65.4.3",
|
"unocss": "^0.58.6",
|
||||||
"vitest": "^3.0.4",
|
"vitest": "^1.4.0",
|
||||||
"vue-tsc": "^2.2.0"
|
"vue-tsc": "^2.0.7"
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"unplugin": "^2.1.2",
|
|
||||||
"vite": "^6.0.11",
|
|
||||||
"vite-plugin-inspect": "^10.1.0"
|
|
||||||
},
|
|
||||||
"simple-git-hooks": {
|
|
||||||
"pre-commit": "pnpm lint-staged"
|
|
||||||
},
|
|
||||||
"lint-staged": {
|
|
||||||
"*": "eslint --fix"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
packages/ai/.env.example
Normal file
1
packages/ai/.env.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
OPENAI_API_KEY=
|
||||||
5
packages/ai/package.json
Normal file
5
packages/ai/package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"openai": "^4.29.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
197
packages/ai/pnpm-lock.yaml
generated
Normal file
197
packages/ai/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
devDependencies:
|
||||||
|
openai:
|
||||||
|
specifier: ^4.29.2
|
||||||
|
version: 4.29.2
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
/@types/node-fetch@2.6.11:
|
||||||
|
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 18.19.26
|
||||||
|
form-data: 4.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/node@18.19.26:
|
||||||
|
resolution: {integrity: sha512-+wiMJsIwLOYCvUqSdKTrfkS8mpTp+MPINe6+Np4TAGFWWRWiBQ5kSq9nZGCSPkzx9mvT+uEukzpX4MOSCydcvw==}
|
||||||
|
dependencies:
|
||||||
|
undici-types: 5.26.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/abort-controller@3.0.0:
|
||||||
|
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||||
|
engines: {node: '>=6.5'}
|
||||||
|
dependencies:
|
||||||
|
event-target-shim: 5.0.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/agentkeepalive@4.5.0:
|
||||||
|
resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
|
||||||
|
engines: {node: '>= 8.0.0'}
|
||||||
|
dependencies:
|
||||||
|
humanize-ms: 1.2.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/base-64@0.1.0:
|
||||||
|
resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/charenc@0.0.2:
|
||||||
|
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/crypt@0.0.2:
|
||||||
|
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/digest-fetch@1.3.0:
|
||||||
|
resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==}
|
||||||
|
dependencies:
|
||||||
|
base-64: 0.1.0
|
||||||
|
md5: 2.3.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/event-target-shim@5.0.1:
|
||||||
|
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/form-data-encoder@1.7.2:
|
||||||
|
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/form-data@4.0.0:
|
||||||
|
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
mime-types: 2.1.35
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/formdata-node@4.4.1:
|
||||||
|
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
|
||||||
|
engines: {node: '>= 12.20'}
|
||||||
|
dependencies:
|
||||||
|
node-domexception: 1.0.0
|
||||||
|
web-streams-polyfill: 4.0.0-beta.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/humanize-ms@1.2.1:
|
||||||
|
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/is-buffer@1.1.6:
|
||||||
|
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/md5@2.3.0:
|
||||||
|
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
|
||||||
|
dependencies:
|
||||||
|
charenc: 0.0.2
|
||||||
|
crypt: 0.0.2
|
||||||
|
is-buffer: 1.1.6
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/ms@2.1.3:
|
||||||
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/node-domexception@1.0.0:
|
||||||
|
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||||
|
engines: {node: '>=10.5.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/node-fetch@2.7.0:
|
||||||
|
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||||
|
engines: {node: 4.x || >=6.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
encoding: ^0.1.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
encoding:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
whatwg-url: 5.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/openai@4.29.2:
|
||||||
|
resolution: {integrity: sha512-cPkT6zjEcE4qU5OW/SoDDuXEsdOLrXlAORhzmaguj5xZSPlgKvLhi27sFWhLKj07Y6WKNWxcwIbzm512FzTBNQ==}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 18.19.26
|
||||||
|
'@types/node-fetch': 2.6.11
|
||||||
|
abort-controller: 3.0.0
|
||||||
|
agentkeepalive: 4.5.0
|
||||||
|
digest-fetch: 1.3.0
|
||||||
|
form-data-encoder: 1.7.2
|
||||||
|
formdata-node: 4.4.1
|
||||||
|
node-fetch: 2.7.0
|
||||||
|
web-streams-polyfill: 3.3.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/tr46@0.0.3:
|
||||||
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/undici-types@5.26.5:
|
||||||
|
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/web-streams-polyfill@3.3.3:
|
||||||
|
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||||
|
engines: {node: '>= 8'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/web-streams-polyfill@4.0.0-beta.3:
|
||||||
|
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/webidl-conversions@3.0.1:
|
||||||
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/whatwg-url@5.0.0:
|
||||||
|
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||||
|
dependencies:
|
||||||
|
tr46: 0.0.3
|
||||||
|
webidl-conversions: 3.0.1
|
||||||
|
dev: true
|
||||||
53
packages/ai/src/api.ts
Normal file
53
packages/ai/src/api.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import consola from 'consola'
|
||||||
|
import type OpenAI from 'openai'
|
||||||
|
import { baseChatCompletionCreateParams, baseModel, config, openai } from './config'
|
||||||
|
|
||||||
|
// TODO: pass params
|
||||||
|
|
||||||
|
export async function getCompletion(msg: string) {
|
||||||
|
const chatCompletion = await openai.chat.completions.create({
|
||||||
|
...baseChatCompletionCreateParams,
|
||||||
|
messages: [{ role: 'user', content: msg }],
|
||||||
|
model: baseModel,
|
||||||
|
})
|
||||||
|
|
||||||
|
return chatCompletion.choices
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 ai 生成的菜谱信息
|
||||||
|
*/
|
||||||
|
export async function getAIRecipeInfo(zhFoods: string[]) {
|
||||||
|
/**
|
||||||
|
* 限制输入长度
|
||||||
|
*/
|
||||||
|
const promptFoods = zhFoods.join('、').slice(0, config.inputMaxLength)
|
||||||
|
|
||||||
|
// 尽可能少的 token
|
||||||
|
const tooltip = `
|
||||||
|
使用以下材料【${promptFoods}】做一道菜,请为这道菜起个名字,最好带有文化底蕴。
|
||||||
|
不要使用生僻字和标点符号。
|
||||||
|
并给出一个有趣的不超过100字的介绍。
|
||||||
|
格式类型:{
|
||||||
|
"名称": "",
|
||||||
|
"介绍": ""
|
||||||
|
}
|
||||||
|
直接给出可以被 JSON.parse 解析的字符串,不需要解释内容。`
|
||||||
|
|
||||||
|
const messages: OpenAI.ChatCompletionMessageParam[] = [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: tooltip,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const chatCompletion = await openai.chat.completions.create({
|
||||||
|
...baseChatCompletionCreateParams,
|
||||||
|
messages,
|
||||||
|
model: baseModel,
|
||||||
|
// stream: true
|
||||||
|
})
|
||||||
|
|
||||||
|
consola.debug(chatCompletion)
|
||||||
|
return chatCompletion.choices[0].message
|
||||||
|
}
|
||||||
26
packages/ai/src/config.ts
Normal file
26
packages/ai/src/config.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import 'dotenv/config'
|
||||||
|
import process from 'node:process'
|
||||||
|
import OpenAI from 'openai'
|
||||||
|
|
||||||
|
const deepseekApiUrl = 'https://api.deepseek.com/v1'
|
||||||
|
|
||||||
|
const aiServiceUrl = process.env.AI_SERVICE_URL || deepseekApiUrl
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
inputMaxLength: 300,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const openai = new OpenAI({
|
||||||
|
baseURL: aiServiceUrl,
|
||||||
|
apiKey: process.env.OPENAI_API_KEY, // This is the default and can be omitted
|
||||||
|
})
|
||||||
|
|
||||||
|
export const baseModel = process.env.MODEL_NAME || 'deepseek-chat'
|
||||||
|
|
||||||
|
export const baseChatCompletionCreateParams: Partial<OpenAI.ChatCompletionCreateParamsNonStreaming> = {
|
||||||
|
max_tokens: 100,
|
||||||
|
// TODO: for use control
|
||||||
|
// presence_penalty: 0,
|
||||||
|
// frequency_penalty: 0,
|
||||||
|
// stream: true
|
||||||
|
}
|
||||||
3
packages/ai/src/index.ts
Normal file
3
packages/ai/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './api'
|
||||||
|
export * from './config'
|
||||||
|
export * from './types'
|
||||||
7
packages/ai/src/types.ts
Normal file
7
packages/ai/src/types.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* AI 食谱
|
||||||
|
*/
|
||||||
|
export interface AIRecipeInfo {
|
||||||
|
名称: string
|
||||||
|
介绍: string
|
||||||
|
}
|
||||||
25
pages/ai.vue
Normal file
25
pages/ai.vue
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'preview',
|
||||||
|
})
|
||||||
|
|
||||||
|
const rStore = useRecipeStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div text-4xl>
|
||||||
|
<button
|
||||||
|
class="cursor-pointer transition active:text-green-800 hover:(text-green-600)"
|
||||||
|
title="重置"
|
||||||
|
@click="rStore.reset"
|
||||||
|
>
|
||||||
|
<div v-if="rStore.selectedStuff.length" i-mdi-pot-steam-outline />
|
||||||
|
<div v-else i-mdi-pot-mix-outline />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p text="sm" m="b-4">
|
||||||
|
好的,今天我们来做菜!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<AiChooseFood />
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defaultCookbook } from '~/utils'
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'child',
|
layout: 'child',
|
||||||
title: '自定义菜谱',
|
title: '自定义菜谱',
|
||||||
165
pages/help.vue
Normal file
165
pages/help.vue
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full">
|
||||||
|
<CommonHeader>
|
||||||
|
帮助
|
||||||
|
</CommonHeader>
|
||||||
|
|
||||||
|
<InstallPwa />
|
||||||
|
|
||||||
|
<FeedbackActions />
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-md w-full rounded-2xl p-2" text-left>
|
||||||
|
<FAQItem title="未来计划?">
|
||||||
|
计划增加新功能,如自定义菜谱,与使用其他用户分享的菜谱。
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="什么是模式?">
|
||||||
|
<ul>
|
||||||
|
<li><b>模糊匹配</b>:展示所有含当前选中任意食材的菜谱</li>
|
||||||
|
<li><b>精准匹配</b>:展示所有含当前选中所有食材的菜谱</li>
|
||||||
|
<li><b>生存模式</b>:展示当前选中食材即可制作的所有菜谱</li>
|
||||||
|
</ul>
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="如何快速清空所选食材和工具?">
|
||||||
|
<div inline-flex items-center justify-center>
|
||||||
|
点击顶部 <div i-mdi-pot-steam-outline mx-1 inline-block /> 图标即可。
|
||||||
|
</div>
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="是否有微信小程序?">
|
||||||
|
因不可抗力(小程序因跳转 B 站视频而被判定为导流违规)下架。
|
||||||
|
将不再提供小程序版本。
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
搜索微信公众号<b>「云游君」</b>并发送<b>「做菜」</b>,也可以快速找到本网站。
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="是否有 APP?">
|
||||||
|
<b>暂时没有开发 APP 的计划。</b>
|
||||||
|
<br>
|
||||||
|
但我们正在优化 <b>PWA</b> 的体验,以便您可以直接将本站添加到桌面,并享受<b>类似 APP 的体验</b>。
|
||||||
|
<br>
|
||||||
|
你可以使用浏览器打开,点击上方的<b>「安装到桌面」</b>或在菜单中点击<b>「添加到主屏幕」</b>。
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="未来是否会收费?">
|
||||||
|
该项目将以免费开源的形式运营。
|
||||||
|
<br>
|
||||||
|
您可以考虑赞助本项目,以支持我们的开发。
|
||||||
|
我会将其投入在周边的服务器、域名、CDN 等费用上。
|
||||||
|
<ul mt-1>
|
||||||
|
<li>
|
||||||
|
<a href="https://afdian.net/a/yunyoujun" target="_blank">爱发电赞助</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://sponsors.yunyoujun.cn/" target="_blank">我要直接打钱!</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="页面无法点击、资源加载失败?">
|
||||||
|
<blockquote>
|
||||||
|
试试「无痕模式」是否正常?
|
||||||
|
</blockquote>
|
||||||
|
<br>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<b>清除 Cookie</b>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
点击浏览器网址前方的 🔒 图标
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
点击「Cookie」并清除
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>强制刷新缓存</b>
|
||||||
|
<ul>
|
||||||
|
<li>Windows: <code>Ctrl + F5</code></li>
|
||||||
|
<li>macOS: <code>Cmd + Shift + R</code></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<hr h="1" my="4" bg-black>
|
||||||
|
|
||||||
|
<FAQItem :default-open="true" title="关于">
|
||||||
|
<div text-left>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
它诞生于 2022 年 4 月,时值疫情风控期间,希望能帮助期间的伙伴根据现有食材寻找到合适的菜谱。故原名「隔离食用手册」。
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
如今那个时期已离我们远去,故去掉「隔离」二字。但也很高兴能在这里继续与你相遇,希望它能继续发光发热,在日常生活中帮助到大家。
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="inline-flex items-center justify-center">
|
||||||
|
代码仓库:<a class="inline-flex items-center justify-center" href="https://github.com/YunYouJun/cook" target="_blank">
|
||||||
|
<div m="r-1" i-ri-github-line inline-flex />YunYouJun/cook</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="inline-flex items-center justify-center">
|
||||||
|
菜谱视频来源:
|
||||||
|
<a class="inline-flex items-center text-sm text-blue-600 dark:text-blue-400" href="https://docs.qq.com/sheet/DQk1vdkhFV0twQVNS" target="_blank">
|
||||||
|
<div m="r-1" i-ri-bilibili-line inline-flex />
|
||||||
|
<span class="inline-flex">隔离食用手册大全</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="关于我">
|
||||||
|
<div text-left>
|
||||||
|
我的个人微信公众号「云游君」,会分享一些生活和写的<a href="https://sponsors.yunyoujun.cn/projects" target="_blank">
|
||||||
|
小玩具们
|
||||||
|
</a>。
|
||||||
|
|
||||||
|
<a inline-flex py-4 href="https://cdn.yunyoujun.cn/img/about/white-qrcode-and-search.jpg" target="_blank">
|
||||||
|
<img src="https://cdn.yunyoujun.cn/img/about/white-qrcode-and-search.jpg">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<AboutMe />
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="致谢">
|
||||||
|
<p>
|
||||||
|
感谢以下小伙伴为本项目提供的数据支持和 QA !
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul mt-2 text-left text-sm>
|
||||||
|
<li>
|
||||||
|
<a href="https://weibo.com/runny" target="_blank">Runny</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
麒麟
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
晴方啾
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
课代表阿伟
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</FAQItem>
|
||||||
|
|
||||||
|
<FAQItem title="赞助者们">
|
||||||
|
<div>
|
||||||
|
感谢至今以来所有的<a href="https://afdian.net/a/yunyoujun" class="text-purple" target="_blank">赞助者</a>们,你们的支持是我持续维护和开发新项目的动力!
|
||||||
|
</div>
|
||||||
|
<div pt-2>
|
||||||
|
<a href="https://sponsors.yunyoujun.cn" target="_blank">
|
||||||
|
<img src="https://sponsors.yunyoujun.cn/sponsors.svg">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FAQItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BaseFooter mt-4 />
|
||||||
|
</template>
|
||||||
22
pages/index.vue
Normal file
22
pages/index.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
const rStore = useRecipeStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div text-4xl>
|
||||||
|
<button
|
||||||
|
class="cursor-pointer transition active:text-green-800 hover:(text-green-600)"
|
||||||
|
title="重置"
|
||||||
|
@click="rStore.reset"
|
||||||
|
>
|
||||||
|
<div v-if="rStore.selectedStuff.length" i-mdi-pot-steam-outline />
|
||||||
|
<div v-else i-mdi-pot-mix-outline />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p text="sm" m="b-4">
|
||||||
|
好的,今天我们来做菜!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ChooseFood />
|
||||||
|
<SimpleCopyright />
|
||||||
|
</template>
|
||||||
@@ -16,21 +16,21 @@ function clearAllHistory() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div pt-2>
|
<div pt-2>
|
||||||
<button
|
<div
|
||||||
text="blue-900 dark:blue-200"
|
text="blue-900 dark:blue-200"
|
||||||
bg="blue-300 op-20 hover:(blue-800 op-20) dark:hover:(blue-200 op-20)"
|
bg="blue-300 op-20 hover:(blue-800 op-20) dark:hover:(blue-200 op-20)"
|
||||||
class="inline-flex items-center justify-center border border-transparent rounded-md px-4 py-2 text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
|
class="inline-flex items-center justify-center border border-transparent rounded-md px-4 py-2 text-sm font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
|
||||||
@click="clearAllHistory"
|
@click="clearAllHistory"
|
||||||
>
|
>
|
||||||
<span i-ri-eraser-line />
|
<div i-ri-eraser-line />
|
||||||
<span class="ml-1">清空记录</span>
|
<span class="ml-1">清空记录</span>
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
<div flex="~ col">
|
<div flex="~ col">
|
||||||
<div v-for="history in recipeHistories" :key="history.recipe.name" mt-2>
|
<div v-for="history in recipeHistories" :key="history.recipe.name" mt-2>
|
||||||
<DateTag>
|
<StapleTag :active="false">
|
||||||
{{ dayjs(history.time).format('YYYY-MM-DD HH:mm:ss') }}
|
{{ dayjs(history.time).format('YYYY-MM-DD HH:mm:ss') }}
|
||||||
</DateTag>
|
</StapleTag>
|
||||||
<DishTag :dish="history.recipe" />
|
<DishTag :dish="history.recipe" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user