diff --git a/adapter/chatgpt/chat.go b/adapter/chatgpt/chat.go index 653615d..d71f2ce 100644 --- a/adapter/chatgpt/chat.go +++ b/adapter/chatgpt/chat.go @@ -122,10 +122,14 @@ func (c *ChatInstance) Test() bool { return err == nil && len(result) > 0 } -func FilterKeys(v string) string { +func FilterKeys(v string) []string { endpoint := viper.GetString(fmt.Sprintf("openai.%s.endpoint", v)) keys := strings.Split(viper.GetString(fmt.Sprintf("openai.%s.apikey", v)), "|") + return FilterKeysNative(endpoint, keys) +} + +func FilterKeysNative(endpoint string, keys []string) []string { stack := make(chan string, len(keys)) for _, key := range keys { go func(key string) { @@ -140,5 +144,5 @@ func FilterKeys(v string) string { result = append(result, res) } } - return strings.Join(result, "|") + return result } diff --git a/app/src/assets/markdown/all.less b/app/src/assets/markdown/all.less index 666155e..94d9fe2 100644 --- a/app/src/assets/markdown/all.less +++ b/app/src/assets/markdown/all.less @@ -27,18 +27,54 @@ } } -.markdown-body pre code { - color: #c9d1d9; -} +.markdown-body { + pre code { + color: #c9d1d9; + } -.markdown-body pre.file-block, -.markdown-body pre.file-block > div { - padding: 0; - margin: 0; - background: none !important; - box-shadow: none !important; + pre.file-block, + pre.file-block > div { + padding: 0; + margin: 0; + background: none !important; + box-shadow: none !important; - &:before { - content: none !important; + &:before { + content: none !important; + } + } + + .markdown-syntax { + position: relative; + + .markdown-syntax-header { + position: absolute; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + z-index: 1; + top: -30px; + right: 2px; + user-select: none; + + p { + font-size: 12px; + line-height: 1; + margin: 0 0 0 6px; + padding: 0; + transform: translateY(-2px); + } + + svg { + cursor: pointer; + color: hsl(var(--text-secondary)); + transition: .2s; + + &:hover { + color: hsl(var(--text)); + } + } + } } } diff --git a/app/src/components/Markdown.tsx b/app/src/components/Markdown.tsx index 275cc9d..f3f705f 100644 --- a/app/src/components/Markdown.tsx +++ b/app/src/components/Markdown.tsx @@ -11,6 +11,10 @@ import { useDispatch } from "react-redux"; import { openDialog as openQuotaDialog } from "../store/quota.ts"; import { openDialog as openSubscriptionDialog } from "../store/subscription.ts"; import { AppDispatch } from "../store"; +import {Copy} from "lucide-react"; +import {copyClipboard} from "../utils.ts"; +import {useToast} from "./ui/use-toast.ts"; +import {useTranslation} from "react-i18next"; type MarkdownProps = { children: string; @@ -30,6 +34,9 @@ function doAction(dispatch: AppDispatch, url: string): boolean { function Markdown({ children, className }: MarkdownProps) { const dispatch = useDispatch(); + const { t } = useTranslation(); + const { toast } = useToast(); + useEffect(() => { document.querySelectorAll(".file-instance").forEach((el) => { @@ -67,17 +74,27 @@ function Markdown({ children, className }: MarkdownProps) { if (match && match[1] === "file") return parseFile(children.toString()); return !inline && match ? ( - +
+
+ { + await copyClipboard(children.toString()); + toast({ + title: t("share.copied"), + }); + }} /> +

{match[1]}

+
+ +
) : ( {children} diff --git a/app/src/conf.ts b/app/src/conf.ts index f686cd1..b56a972 100644 --- a/app/src/conf.ts +++ b/app/src/conf.ts @@ -1,7 +1,7 @@ import axios from "axios"; import { Model } from "./conversation/types.ts"; -export const version = "3.5.19"; +export const version = "3.5.20"; export const dev: boolean = window.location.hostname === "localhost"; export const deploy: boolean = true; export let rest_api: string = "http://localhost:8094"; diff --git a/app/src/store/api.ts b/app/src/store/api.ts index 5ad7fd8..fc0984e 100644 --- a/app/src/store/api.ts +++ b/app/src/store/api.ts @@ -22,6 +22,7 @@ export const apiSlice = createSlice({ state.dialog = false; }, setKey: (state, action) => { + if (!action.payload.length) return; state.key = action.payload as string; }, }, diff --git a/cli/exec.go b/cli/exec.go index 6a33a7a..b791c7a 100644 --- a/cli/exec.go +++ b/cli/exec.go @@ -13,6 +13,9 @@ func Run() bool { case "invite": CreateInvitationCommand(args[1:]) return true + case "filter": + FilterApiKeyCommand(args[1:]) + return true default: return false } diff --git a/cli/filter.go b/cli/filter.go new file mode 100644 index 0000000..4b5a433 --- /dev/null +++ b/cli/filter.go @@ -0,0 +1,18 @@ +package cli + +import ( + "chat/adapter/chatgpt" + "fmt" + "github.com/spf13/viper" + "strings" +) + +func FilterApiKeyCommand(args []string) { + data := strings.Trim(strings.TrimSpace(GetArgString(args, 0)), "\"") + endpoint := viper.GetString("openai.test") + keys := strings.Split(data, "|") + + available := chatgpt.FilterKeysNative(endpoint, keys) + fmt.Println(fmt.Sprintf("[cli] filtered %d keys, %d available, %d unavailable", len(keys), len(available), len(keys)-len(available))) + fmt.Println(strings.Join(available, "|")) +}