add markdown code copy feature

This commit is contained in:
Zhang Minghan 2023-10-28 22:31:02 +08:00
parent cf67af6df4
commit 83213a60fb
7 changed files with 104 additions and 25 deletions

View File

@ -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
}

View File

@ -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));
}
}
}
}
}

View File

@ -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 ? (
<SyntaxHighlighter
{...props}
children={String(children).replace(/\n$/, "")}
style={style}
language={match[1]}
PreTag="div"
wrapLongLines={true}
wrapLines={true}
className={`code-block`}
lang={match[1]}
/>
<div className={`markdown-syntax`}>
<div className={`markdown-syntax-header`}>
<Copy className={`h-3 w-3`} onClick={async () => {
await copyClipboard(children.toString());
toast({
title: t("share.copied"),
});
}} />
<p>{match[1]}</p>
</div>
<SyntaxHighlighter
{...props}
children={String(children).replace(/\n$/, "")}
style={style}
language={match[1]}
PreTag="div"
wrapLongLines={true}
wrapLines={true}
className={`code-block`}
/>
</div>
) : (
<code className={`code-inline ${className}`} {...props}>
{children}

View File

@ -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";

View File

@ -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;
},
},

View File

@ -13,6 +13,9 @@ func Run() bool {
case "invite":
CreateInvitationCommand(args[1:])
return true
case "filter":
FilterApiKeyCommand(args[1:])
return true
default:
return false
}

18
cli/filter.go Normal file
View File

@ -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, "|"))
}