mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 21:10:18 +09:00
add markdown code copy feature
This commit is contained in:
parent
cf67af6df4
commit
83213a60fb
@ -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
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
},
|
||||
},
|
||||
|
@ -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
18
cli/filter.go
Normal 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, "|"))
|
||||
}
|
Loading…
Reference in New Issue
Block a user