mirror of
https://github.com/coaidev/coai.git
synced 2025-05-20 05:20:15 +09:00
update frontend
This commit is contained in:
parent
981b7e2e64
commit
a5e18a1ee2
@ -55,6 +55,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.message-toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 4px;
|
||||||
|
user-select: none;
|
||||||
|
height: max-content;
|
||||||
|
margin-top: auto;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
cursor: pointer;
|
||||||
|
color: hsl(var(--text-secondary));
|
||||||
|
transition: 0.25s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: hsl(var(--text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.message-quota {
|
.message-quota {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
Copy,
|
Copy,
|
||||||
File,
|
File,
|
||||||
Loader2,
|
Loader2,
|
||||||
MousePointerSquare,
|
MousePointerSquare, Power, RotateCcw,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
@ -30,16 +30,19 @@ import {
|
|||||||
|
|
||||||
type MessageProps = {
|
type MessageProps = {
|
||||||
message: Message;
|
message: Message;
|
||||||
|
end?: boolean;
|
||||||
|
onEvent?: (event: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MessageSegment({ message }: MessageProps) {
|
function MessageSegment(props: MessageProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { message } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuTrigger asChild>
|
<ContextMenuTrigger asChild>
|
||||||
<div className={`message ${message.role}`}>
|
<div className={`message ${message.role}`}>
|
||||||
<MessageContent message={message} />
|
<MessageContent {...props} />
|
||||||
{message.quota && message.quota !== 0 ? (
|
{message.quota && message.quota !== 0 ? (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@ -86,9 +89,9 @@ function MessageSegment({ message }: MessageProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MessageContent({ message }: MessageProps) {
|
function MessageContent({ message, end, onEvent }: MessageProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={`content-wrapper`}>
|
||||||
<div className={`message-content`}>
|
<div className={`message-content`}>
|
||||||
{message.keyword && message.keyword.length ? (
|
{message.keyword && message.keyword.length ? (
|
||||||
<div className={`bing`}>
|
<div className={`bing`}>
|
||||||
@ -162,7 +165,22 @@ function MessageContent({ message }: MessageProps) {
|
|||||||
<Loader2 className={`h-5 w-5 m-1 animate-spin`} />
|
<Loader2 className={`h-5 w-5 m-1 animate-spin`} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
{
|
||||||
|
(message.role === "assistant" && end === true) && (
|
||||||
|
<div className={`message-toolbar`}>
|
||||||
|
{
|
||||||
|
(message.end !== false) ?
|
||||||
|
<RotateCcw className={`h-4 w-4 m-0.5`} onClick={() => (
|
||||||
|
onEvent && onEvent("restart")
|
||||||
|
)} /> :
|
||||||
|
<Power className={`h-4 w-4 m-0.5`} onClick={() => (
|
||||||
|
onEvent && onEvent("stop")
|
||||||
|
)} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
export const version = "3.4.4";
|
export const version = "3.4.5";
|
||||||
export const deploy: boolean = false;
|
export const deploy: boolean = true;
|
||||||
export let rest_api: string = "http://localhost:8094";
|
export let rest_api: string = "http://localhost:8094";
|
||||||
export let ws_api: string = "ws://localhost:8094";
|
export let ws_api: string = "ws://localhost:8094";
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ChatProps, Connection, StreamMessage } from "./connection.ts";
|
import { ChatProps, Connection, StreamMessage } from "./connection.ts";
|
||||||
import { Message } from "./types.ts";
|
import { Message } from "./types.ts";
|
||||||
import { event } from "../events/sharing.ts";
|
import { sharingEvent } from "../events/sharing.ts";
|
||||||
|
import {connectionEvent} from "../events/connection.ts";
|
||||||
|
|
||||||
type ConversationCallback = (idx: number, message: Message[]) => void;
|
type ConversationCallback = (idx: number, message: Message[]) => void;
|
||||||
|
|
||||||
@ -11,7 +12,6 @@ export class Conversation {
|
|||||||
public id: number;
|
public id: number;
|
||||||
public data: Message[];
|
public data: Message[];
|
||||||
public end: boolean;
|
public end: boolean;
|
||||||
public refer: string;
|
|
||||||
|
|
||||||
public constructor(id: number, callback?: ConversationCallback) {
|
public constructor(id: number, callback?: ConversationCallback) {
|
||||||
if (callback) this.setCallback(callback);
|
if (callback) this.setCallback(callback);
|
||||||
@ -20,23 +20,50 @@ export class Conversation {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.end = true;
|
this.end = true;
|
||||||
this.connection = new Connection(this.id);
|
this.connection = new Connection(this.id);
|
||||||
this.refer = "";
|
|
||||||
|
|
||||||
if (id === -1 && this.idx === -1) {
|
if (id === -1 && this.idx === -1) {
|
||||||
event.bind(({ refer, data }) => {
|
sharingEvent.bind(({ refer, data }) => {
|
||||||
console.log(
|
console.log(
|
||||||
`[conversation] load from sharing event (ref: ${refer}, length: ${data.length})`,
|
`[conversation] load from sharing event (ref: ${refer}, length: ${data.length})`,
|
||||||
);
|
);
|
||||||
this.refer = refer;
|
|
||||||
this.load(data);
|
|
||||||
|
|
||||||
this.connection?.sendWithRetry(null, {
|
this.load(data);
|
||||||
type: "share",
|
this.sendEvent("share", refer);
|
||||||
message: this.refer,
|
|
||||||
model: "gpt-3.5-turbo",
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectionEvent.addEventListener((ev) => {
|
||||||
|
if (ev.id === this.id) {
|
||||||
|
console.debug(`[conversation] connection event (id: ${this.id}, event: ${ev.event})`);
|
||||||
|
|
||||||
|
switch (ev.event) {
|
||||||
|
case "stop":
|
||||||
|
this.end = true;
|
||||||
|
this.data[this.data.length - 1].end = true;
|
||||||
|
this.sendEvent("stop");
|
||||||
|
this.triggerCallback();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "restart":
|
||||||
|
this.end = false;
|
||||||
|
delete this.data[this.data.length - 1];
|
||||||
|
this.connection?.setCallback(this.useMessage());
|
||||||
|
this.sendEvent("restart");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.debug(`[conversation] unknown event: ${ev.event} (from: ${ev.id})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected sendEvent(event: string, data?: string) {
|
||||||
|
this.connection?.sendWithRetry(null, {
|
||||||
|
type: event,
|
||||||
|
message: data || "",
|
||||||
|
model: "event",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setId(id: number): void {
|
public setId(id: number): void {
|
||||||
@ -98,10 +125,12 @@ export class Conversation {
|
|||||||
message: string,
|
message: string,
|
||||||
keyword?: string,
|
keyword?: string,
|
||||||
quota?: number,
|
quota?: number,
|
||||||
|
end?: boolean,
|
||||||
) {
|
) {
|
||||||
this.data[idx].content += message;
|
this.data[idx].content += message;
|
||||||
if (keyword) this.data[idx].keyword = keyword;
|
if (keyword) this.data[idx].keyword = keyword;
|
||||||
if (quota) this.data[idx].quota = quota;
|
if (quota) this.data[idx].quota = quota;
|
||||||
|
this.data[idx].end = end;
|
||||||
this.triggerCallback();
|
this.triggerCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +146,7 @@ export class Conversation {
|
|||||||
message.message,
|
message.message,
|
||||||
message.keyword,
|
message.keyword,
|
||||||
message.quota,
|
message.quota,
|
||||||
|
message.end,
|
||||||
);
|
);
|
||||||
if (message.end) {
|
if (message.end) {
|
||||||
this.end = true;
|
this.end = true;
|
||||||
|
@ -11,7 +11,7 @@ import { useShared } from "../utils.ts";
|
|||||||
import { ChatProps } from "./connection.ts";
|
import { ChatProps } from "./connection.ts";
|
||||||
import { supportModelConvertor } from "../conf.ts";
|
import { supportModelConvertor } from "../conf.ts";
|
||||||
import { AppDispatch } from "../store";
|
import { AppDispatch } from "../store";
|
||||||
import { event } from "../events/sharing.ts";
|
import { sharingEvent } from "../events/sharing.ts";
|
||||||
|
|
||||||
export class Manager {
|
export class Manager {
|
||||||
conversations: Record<number, Conversation>;
|
conversations: Record<number, Conversation>;
|
||||||
@ -23,7 +23,7 @@ export class Manager {
|
|||||||
this.conversations[-1] = this.createConversation(-1);
|
this.conversations[-1] = this.createConversation(-1);
|
||||||
this.current = -1;
|
this.current = -1;
|
||||||
|
|
||||||
event.addEventListener(async (data) => {
|
sharingEvent.addEventListener(async (data) => {
|
||||||
console.debug(`[manager] accept sharing event (refer: ${data.refer})`);
|
console.debug(`[manager] accept sharing event (refer: ${data.refer})`);
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
|
@ -5,6 +5,7 @@ export type Message = {
|
|||||||
content: string;
|
content: string;
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
quota?: number;
|
quota?: number;
|
||||||
|
end?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Id = number;
|
export type Id = number;
|
||||||
|
10
app/src/events/connection.ts
Normal file
10
app/src/events/connection.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import {EventCommitter} from "./struct.ts";
|
||||||
|
|
||||||
|
export type ConnectionEvent = {
|
||||||
|
id: number;
|
||||||
|
event: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const connectionEvent = new EventCommitter<ConnectionEvent>({
|
||||||
|
name: "connection",
|
||||||
|
});
|
@ -6,7 +6,7 @@ export type SharingEvent = {
|
|||||||
data: Message[];
|
data: Message[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const event = new EventCommitter<SharingEvent>({
|
export const sharingEvent = new EventCommitter<SharingEvent>({
|
||||||
name: "sharing",
|
name: "sharing",
|
||||||
destroyedAfterTrigger: true,
|
destroyedAfterTrigger: true,
|
||||||
});
|
});
|
||||||
|
@ -69,6 +69,7 @@ import router from "../router.ts";
|
|||||||
import SelectGroup from "../components/SelectGroup.tsx";
|
import SelectGroup from "../components/SelectGroup.tsx";
|
||||||
import EditorProvider from "../components/EditorProvider.tsx";
|
import EditorProvider from "../components/EditorProvider.tsx";
|
||||||
import ConversationSegment from "../components/home/ConversationSegment.tsx";
|
import ConversationSegment from "../components/home/ConversationSegment.tsx";
|
||||||
|
import {connectionEvent} from "../events/connection.ts";
|
||||||
|
|
||||||
function SideBar() {
|
function SideBar() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -307,6 +308,7 @@ function ChatInterface() {
|
|||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const [scroll, setScroll] = useState(false);
|
const [scroll, setScroll] = useState(false);
|
||||||
const messages: Message[] = useSelector(selectMessages);
|
const messages: Message[] = useSelector(selectMessages);
|
||||||
|
const current: number = useSelector(selectCurrent);
|
||||||
|
|
||||||
function listenScrolling() {
|
function listenScrolling() {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
@ -351,9 +353,21 @@ function ChatInterface() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{messages.map((message, i) => (
|
{
|
||||||
<MessageSegment message={message} key={i} />
|
messages.map((message, i) =>
|
||||||
))}
|
<MessageSegment
|
||||||
|
message={message}
|
||||||
|
end={i === messages.length - 1}
|
||||||
|
onEvent={(e: string) => {
|
||||||
|
connectionEvent.emit({
|
||||||
|
id: current,
|
||||||
|
event: e,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
key={i}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,7 @@ import MessageSegment from "../components/Message.tsx";
|
|||||||
import { Button } from "../components/ui/button.tsx";
|
import { Button } from "../components/ui/button.tsx";
|
||||||
import router from "../router.ts";
|
import router from "../router.ts";
|
||||||
import { useToast } from "../components/ui/use-toast.ts";
|
import { useToast } from "../components/ui/use-toast.ts";
|
||||||
import { event } from "../events/sharing.ts";
|
import { sharingEvent } from "../events/sharing.ts";
|
||||||
import { Message } from "../conversation/types.ts";
|
import { Message } from "../conversation/types.ts";
|
||||||
|
|
||||||
type SharingFormProps = {
|
type SharingFormProps = {
|
||||||
@ -72,7 +72,7 @@ function SharingForm({ refer, data }: SharingFormProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant={`outline`}
|
variant={`outline`}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
event.emit({
|
sharingEvent.emit({
|
||||||
refer: refer as string,
|
refer: refer as string,
|
||||||
data: data?.messages as Message[],
|
data: data?.messages as Message[],
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMessage = "Sorry, I don't understand. Please try again."
|
const defaultMessage = "Sorry, I don't understand. Please try again."
|
||||||
@ -53,6 +54,27 @@ func ImageHandler(conn *Connection, user *auth.User, instance *conversation.Conv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MockStreamSender(conn *Connection, message string) {
|
||||||
|
for _, line := range utils.SplitLangItems(message) {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
conn.Send(globals.ChatSegmentResponse{
|
||||||
|
Message: line + " ",
|
||||||
|
End: false,
|
||||||
|
Quota: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
if signal := conn.PeekWithType(StopType); signal != nil {
|
||||||
|
// stop signal from client
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Send(globals.ChatSegmentResponse{
|
||||||
|
End: true,
|
||||||
|
Quota: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func ChatHandler(conn *Connection, user *auth.User, instance *conversation.Conversation) string {
|
func ChatHandler(conn *Connection, user *auth.User, instance *conversation.Conversation) string {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
@ -84,16 +106,12 @@ func ChatHandler(conn *Connection, user *auth.User, instance *conversation.Conve
|
|||||||
Model: model,
|
Model: model,
|
||||||
Reversible: reversible,
|
Reversible: reversible,
|
||||||
}); form != nil {
|
}); form != nil {
|
||||||
conn.Send(globals.ChatSegmentResponse{
|
MockStreamSender(conn, form.Message)
|
||||||
Message: form.Message,
|
|
||||||
Quota: 0,
|
|
||||||
End: true,
|
|
||||||
})
|
|
||||||
return form.Message
|
return form.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := utils.NewBuffer(model, segment)
|
buffer := utils.NewBuffer(model, segment)
|
||||||
if err := adapter.NewChatRequest(&adapter.ChatProps{
|
err := adapter.NewChatRequest(&adapter.ChatProps{
|
||||||
Model: model,
|
Model: model,
|
||||||
Message: segment,
|
Message: segment,
|
||||||
Reversible: reversible && globals.IsGPT4Model(model),
|
Reversible: reversible && globals.IsGPT4Model(model),
|
||||||
@ -107,7 +125,9 @@ func ChatHandler(conn *Connection, user *auth.User, instance *conversation.Conve
|
|||||||
Quota: buffer.GetQuota(),
|
Quota: buffer.GetQuota(),
|
||||||
End: false,
|
End: false,
|
||||||
})
|
})
|
||||||
}); err != nil && err.Error() != "signal" {
|
})
|
||||||
|
|
||||||
|
if err != nil && err.Error() != "signal" {
|
||||||
CollectQuota(conn.GetCtx(), user, buffer.GetQuota(), reversible)
|
CollectQuota(conn.GetCtx(), user, buffer.GetQuota(), reversible)
|
||||||
conn.Send(globals.ChatSegmentResponse{
|
conn.Send(globals.ChatSegmentResponse{
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
@ -130,14 +150,18 @@ func ChatHandler(conn *Connection, user *auth.User, instance *conversation.Conve
|
|||||||
|
|
||||||
conn.Send(globals.ChatSegmentResponse{End: true, Quota: buffer.GetQuota()})
|
conn.Send(globals.ChatSegmentResponse{End: true, Quota: buffer.GetQuota()})
|
||||||
|
|
||||||
SaveCacheData(conn.GetCtx(), &CacheProps{
|
result := buffer.ReadWithDefault(defaultMessage)
|
||||||
Message: segment,
|
|
||||||
Model: model,
|
|
||||||
Reversible: reversible,
|
|
||||||
}, &CacheData{
|
|
||||||
Keyword: keyword,
|
|
||||||
Message: buffer.ReadWithDefault(defaultMessage),
|
|
||||||
})
|
|
||||||
|
|
||||||
return buffer.ReadWithDefault(defaultMessage)
|
if err == nil && result != defaultMessage {
|
||||||
|
SaveCacheData(conn.GetCtx(), &CacheProps{
|
||||||
|
Message: segment,
|
||||||
|
Model: model,
|
||||||
|
Reversible: reversible,
|
||||||
|
}, &CacheData{
|
||||||
|
Keyword: keyword,
|
||||||
|
Message: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,9 @@ func ExtractConversation(db *sql.DB, user *auth.User, id int64, ref string) *Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conversation) GetModel() string {
|
func (c *Conversation) GetModel() string {
|
||||||
|
if len(c.Model) == 0 {
|
||||||
|
return globals.GPT3Turbo
|
||||||
|
}
|
||||||
return c.Model
|
return c.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +85,9 @@ func (c *Conversation) IsEnableWeb() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conversation) SetModel(model string) {
|
func (c *Conversation) SetModel(model string) {
|
||||||
|
if len(model) == 0 {
|
||||||
|
model = globals.GPT3Turbo
|
||||||
|
}
|
||||||
c.Model = model
|
c.Model = model
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +252,9 @@ func (c *Conversation) SaveResponse(db *sql.DB, message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conversation) RemoveMessage(index int) globals.Message {
|
func (c *Conversation) RemoveMessage(index int) globals.Message {
|
||||||
|
if index < 0 || index >= len(c.Message) {
|
||||||
|
return globals.Message{}
|
||||||
|
}
|
||||||
message := c.Message[index]
|
message := c.Message[index]
|
||||||
c.Message = append(c.Message[:index], c.Message[index+1:]...)
|
c.Message = append(c.Message[:index], c.Message[index+1:]...)
|
||||||
return message
|
return message
|
||||||
|
@ -62,7 +62,7 @@ func ChatAPI(c *gin.Context) {
|
|||||||
case ShareType:
|
case ShareType:
|
||||||
instance.LoadSharing(db, form.Message)
|
instance.LoadSharing(db, form.Message)
|
||||||
case RestartType:
|
case RestartType:
|
||||||
if message := instance.RemoveLatestMessage(); message.Role != "user" {
|
if message := instance.RemoveLatestMessage(); message.Role != "assistant" {
|
||||||
return fmt.Errorf("message type error")
|
return fmt.Errorf("message type error")
|
||||||
}
|
}
|
||||||
response := EventHandler(buf, instance, user)
|
response := EventHandler(buf, instance, user)
|
||||||
|
@ -103,6 +103,26 @@ func SplitItem(data string, sep string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitItems(data string, seps []string) []string {
|
||||||
|
if len(seps) == 0 {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := []string{data}
|
||||||
|
for _, sep := range seps {
|
||||||
|
var temp []string
|
||||||
|
for _, item := range result {
|
||||||
|
temp = append(temp, SplitItem(item, sep)...)
|
||||||
|
}
|
||||||
|
result = temp
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitLangItems(data string) []string {
|
||||||
|
return SplitItems(data, []string{",", ",", " ", "\n"})
|
||||||
|
}
|
||||||
|
|
||||||
func Extract(data string, length int, flow string) string {
|
func Extract(data string, length int, flow string) string {
|
||||||
value := []rune(data)
|
value := []rune(data)
|
||||||
if len(value) > length {
|
if len(value) > length {
|
||||||
|
Loading…
Reference in New Issue
Block a user