mirror of
https://github.com/coaidev/coai.git
synced 2025-05-28 09:20:18 +09:00
update gpt-4-vision-preview model adapter support
This commit is contained in:
parent
cec50114c6
commit
a8b3f6cc7c
@ -2,12 +2,24 @@ package adapter
|
||||
|
||||
import (
|
||||
"chat/globals"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func IsAvailableError(err error) bool {
|
||||
return err != nil && err.Error() != "signal"
|
||||
}
|
||||
|
||||
func isQPSOverLimit(model string, err error) bool {
|
||||
switch model {
|
||||
case globals.SparkDesk, globals.SparkDeskV2, globals.SparkDeskV3:
|
||||
return strings.Contains(err.Error(), "AppIdQpsOverFlowError")
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getRetries(retries *int) int {
|
||||
if retries == nil {
|
||||
return defaultMaxRetries
|
||||
@ -19,11 +31,23 @@ func getRetries(retries *int) int {
|
||||
func NewChatRequest(props *ChatProps, hook globals.Hook) error {
|
||||
err := createChatRequest(props, hook)
|
||||
|
||||
props.Current++
|
||||
retries := getRetries(props.MaxRetries)
|
||||
props.Current++
|
||||
if props.Current > 1 {
|
||||
fmt.Println(fmt.Sprintf("retrying chat request for %s (attempt %d/%d, error: %s)", props.Model, props.Current, retries, err.Error()))
|
||||
}
|
||||
|
||||
if IsAvailableError(err) && props.Current < retries {
|
||||
return NewChatRequest(props, hook)
|
||||
if IsAvailableError(err) {
|
||||
if isQPSOverLimit(props.Model, err) {
|
||||
// sleep for 0.5s to avoid qps limit
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
return NewChatRequest(props, hook)
|
||||
}
|
||||
|
||||
if props.Current < retries {
|
||||
return NewChatRequest(props, hook)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -48,11 +48,11 @@ func CreateGenerationWorker(c *gin.Context, user *auth.User, model string, promp
|
||||
titles := ParseTitle(title)
|
||||
result := make(chan Response, len(titles))
|
||||
|
||||
go func() {
|
||||
for _, title := range titles {
|
||||
for _, title := range titles {
|
||||
go func(title string) {
|
||||
result <- GenerateArticle(c, user, model, hash, title, prompt, enableWeb)
|
||||
}
|
||||
}()
|
||||
}(title)
|
||||
}
|
||||
|
||||
return len(titles), result
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ export const modelColorMapper: Record<string, string> = {
|
||||
|
||||
"gpt-4": "#8e43e7",
|
||||
"gpt-4-1106-preview": "#8e43e7",
|
||||
"gpt-4-vision-preview": "#8e43e7",
|
||||
"gpt-4-0613": "#8e43e7",
|
||||
"gpt-4-0314": "#8e43e7",
|
||||
"gpt-4-all": "#8e43e7",
|
||||
@ -64,6 +65,11 @@ export const modelColorMapper: Record<string, string> = {
|
||||
|
||||
hunyuan: "#0052d9",
|
||||
"360-gpt-v9": "#1db91e",
|
||||
"baichuan-53b": "#ff9800",
|
||||
"skylark-lite-public": "#a4f2ff",
|
||||
"skylark-plus-public": "#a4f2ff",
|
||||
"skylark-pro-public": "#a4f2ff",
|
||||
"skylark-chat": "#a4f2ff",
|
||||
};
|
||||
|
||||
export function getModelColor(model: string): string {
|
||||
|
@ -99,6 +99,19 @@ strong {
|
||||
transform: translate(var(--tw-translate-x), calc(var(--tw-translate-y) - 1rem)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
color: hsl(var(--text-secondary));
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease-in-out;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
margin: 0.5rem auto;
|
||||
|
||||
&:hover {
|
||||
color: hsl(var(--text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cent {
|
||||
|
@ -21,6 +21,7 @@
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
max-width: calc(90vw - 3rem);
|
||||
margin: 0.5rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +36,7 @@
|
||||
}
|
||||
|
||||
.quota-dialog {
|
||||
max-width: min(90vw, 1044px) !important;
|
||||
max-width: min(90vw, 844px) !important;
|
||||
}
|
||||
|
||||
.amount-container {
|
||||
|
@ -47,7 +47,11 @@ function MessageSegment(props: MessageProps) {
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className={`message-quota ${message.plan ? "subscription" : ""}`}>
|
||||
<div
|
||||
className={`message-quota ${
|
||||
message.plan ? "subscription" : ""
|
||||
}`}
|
||||
>
|
||||
<Cloud className={`h-4 w-4 icon`} />
|
||||
<span className={`quota`}>
|
||||
{(message.quota < 0 ? 0 : message.quota).toFixed(2)}
|
||||
|
@ -12,10 +12,10 @@ import {
|
||||
} from "@/components/ui/dropdown-menu.tsx";
|
||||
import { Button } from "@/components/ui/button.tsx";
|
||||
import {
|
||||
BadgeCent,
|
||||
Boxes,
|
||||
CalendarPlus,
|
||||
Cloud,
|
||||
Cloudy,
|
||||
Gift,
|
||||
ListStart,
|
||||
Plug,
|
||||
@ -52,7 +52,7 @@ function MenuBar({ children, className }: MenuBarProps) {
|
||||
{quota}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => dispatch(openQuotaDialog())}>
|
||||
<BadgeCent className={`h-4 w-4 mr-1`} />
|
||||
<Cloudy className={`h-4 w-4 mr-1`} />
|
||||
{t("quota")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => dispatch(openSub())}>
|
||||
|
@ -19,7 +19,7 @@ function ChatInput({
|
||||
onEnterPressed,
|
||||
}: ChatInputProps) {
|
||||
const { t } = useTranslation();
|
||||
const [stamp, setStamp] = React.useState(0);
|
||||
const [pressed, setPressed] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Textarea
|
||||
@ -35,14 +35,19 @@ function ChatInput({
|
||||
placeholder={t("chat.placeholder")}
|
||||
onKeyDown={async (e) => {
|
||||
if (e.key === "Control") {
|
||||
setStamp(Date.now());
|
||||
setPressed(true);
|
||||
} else if (e.key === "Enter" && !e.shiftKey) {
|
||||
if (stamp > 0 && Date.now() - stamp < 200) {
|
||||
if (pressed) {
|
||||
e.preventDefault();
|
||||
onEnterPressed();
|
||||
}
|
||||
}
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Control") {
|
||||
setTimeout(() => setPressed(false), 250);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from "@/utils/env.ts";
|
||||
import { getMemory } from "@/utils/memory.ts";
|
||||
|
||||
export const version = "3.6.33";
|
||||
export const version = "3.6.34";
|
||||
export const dev: boolean = getDev();
|
||||
export const deploy: boolean = true;
|
||||
export let rest_api: string = getRestApi(deploy);
|
||||
@ -46,10 +46,17 @@ export const supportModels: Model[] = [
|
||||
},
|
||||
{
|
||||
id: "gpt-4-1106-preview",
|
||||
name: "GPT-4 Turbo",
|
||||
name: "GPT-4 Turbo 128k",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-context"],
|
||||
tag: ["official", "high-context", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-vision-preview",
|
||||
name: "GPT-4 Vision 128k",
|
||||
free: false,
|
||||
auth: true,
|
||||
tag: ["official", "high-context", "multi-modal", "unstable"],
|
||||
},
|
||||
{
|
||||
id: "gpt-4-v",
|
||||
@ -321,6 +328,7 @@ export const defaultModels = [
|
||||
export const largeContextModels = [
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-4-all",
|
||||
"gpt-4-32k-0613",
|
||||
"claude-1",
|
||||
@ -335,6 +343,7 @@ export const studentModels = ["claude-2-100k", "claude-2"];
|
||||
export const planModels = [
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-4-v",
|
||||
"gpt-4-all",
|
||||
"gpt-4-dalle",
|
||||
@ -355,6 +364,7 @@ export const modelAvatars: Record<string, string> = {
|
||||
"gpt-3.5-turbo-1106": "gpt35turbo16k.webp",
|
||||
"gpt-4-0613": "gpt4.png",
|
||||
"gpt-4-1106-preview": "gpt432k.webp",
|
||||
"gpt-4-vision-preview": "gpt4v.png",
|
||||
"gpt-4-all": "gpt4.png",
|
||||
"gpt-4-32k-0613": "gpt432k.webp",
|
||||
"gpt-4-v": "gpt4v.png",
|
||||
|
@ -5,6 +5,10 @@ import {
|
||||
refreshQuota,
|
||||
setDialog,
|
||||
} from "@/store/quota.ts";
|
||||
import {
|
||||
openDialog as openSubDialog,
|
||||
dialogSelector as subDialogSelector,
|
||||
} from "@/store/subscription.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
@ -15,18 +19,10 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog.tsx";
|
||||
import "@/assets/pages/quota.less";
|
||||
import {
|
||||
Cloud,
|
||||
ExternalLink,
|
||||
HardDriveDownload,
|
||||
HardDriveUpload,
|
||||
Info,
|
||||
Plus,
|
||||
} from "lucide-react";
|
||||
import { Cloud, ExternalLink, Plus } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import { testNumberInputEvent } from "@/utils/dom.ts";
|
||||
import { Button } from "@/components/ui/button.tsx";
|
||||
import { Separator } from "@/components/ui/separator.tsx";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@ -84,6 +80,8 @@ function QuotaDialog() {
|
||||
const open = useSelector(dialogSelector);
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
|
||||
const sub = useSelector(subDialogSelector);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
useEffectAsync(async () => {
|
||||
if (!auth) return;
|
||||
@ -103,6 +101,14 @@ function QuotaDialog() {
|
||||
<DialogTitle>{t("buy.choose")}</DialogTitle>
|
||||
<DialogDescription asChild>
|
||||
<div className={`dialog-wrapper`}>
|
||||
<p
|
||||
className={`link translate-y-2 text-center`}
|
||||
onClick={() =>
|
||||
sub ? dispatch(closeDialog()) : dispatch(openSubDialog())
|
||||
}
|
||||
>
|
||||
{t("sub.subscription-link")}
|
||||
</p>
|
||||
<div className={`buy-interface`}>
|
||||
<div className={`interface-item`}>
|
||||
<div className={`amount-container`}>
|
||||
@ -131,14 +137,30 @@ function QuotaDialog() {
|
||||
setAmount(250);
|
||||
}}
|
||||
/>
|
||||
<AmountComponent
|
||||
amount={50}
|
||||
active={current === 4}
|
||||
onClick={() => {
|
||||
setCurrent(4);
|
||||
setAmount(500);
|
||||
}}
|
||||
/>
|
||||
<AmountComponent
|
||||
amount={100}
|
||||
active={current === 5}
|
||||
onClick={() => {
|
||||
setCurrent(5);
|
||||
setAmount(1000);
|
||||
}}
|
||||
/>
|
||||
<AmountComponent
|
||||
amount={NaN}
|
||||
other={true}
|
||||
active={current === 4}
|
||||
onClick={() => setCurrent(4)}
|
||||
active={current === 6}
|
||||
onClick={() => setCurrent(6)}
|
||||
/>
|
||||
</div>
|
||||
{current === 4 && (
|
||||
{current === 6 && (
|
||||
<div className={`other-wrapper`}>
|
||||
<div className={`amount-input-box`}>
|
||||
<Cloud className={`h-4 w-4`} />
|
||||
@ -240,78 +262,6 @@ function QuotaDialog() {
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`line`} />
|
||||
<div className={`interface-item grow`}>
|
||||
<div className={`product-item`}>
|
||||
<div className={`row title`}>
|
||||
<div>GPT-4</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`column`}>
|
||||
<Cloud className={`h-4 w-4`} /> {t("buy.flex")}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`row desc`}>
|
||||
<div className={`column`}>
|
||||
<HardDriveUpload className={`h-4 w-4`} />
|
||||
{t("buy.input")}
|
||||
</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`column`}>
|
||||
<Cloud className={`h-4 w-4`} />
|
||||
2.1 / 1k token
|
||||
</div>
|
||||
</div>
|
||||
<div className={`row desc`}>
|
||||
<div className={`column`}>
|
||||
<HardDriveDownload className={`h-4 w-4`} />
|
||||
{t("buy.output")}
|
||||
</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`column`}>
|
||||
<Cloud className={`h-4 w-4`} />
|
||||
4.3 / 1k token
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator orientation={`horizontal`} className={`my-2`} />
|
||||
<div className={`product-item`}>
|
||||
<div className={`row title`}>
|
||||
<div>GPT-4-32K</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`column`}>
|
||||
<Cloud className={`h-4 w-4`} /> {t("buy.flex")}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`row desc`}>
|
||||
<div className={`column`}>
|
||||
<HardDriveUpload className={`h-4 w-4`} />
|
||||
{t("buy.input")}
|
||||
</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`column`}>
|
||||
<Cloud className={`h-4 w-4`} />
|
||||
4.2 / 1k token
|
||||
</div>
|
||||
</div>
|
||||
<div className={`row desc`}>
|
||||
<div className={`column`}>
|
||||
<HardDriveDownload className={`h-4 w-4`} />
|
||||
{t("buy.output")}
|
||||
</div>
|
||||
<div className={`grow`} />
|
||||
<div className={`column`}>
|
||||
<Cloud className={`h-4 w-4`} />
|
||||
8.6 / 1k token
|
||||
</div>
|
||||
</div>
|
||||
<div className={`row desc`}>
|
||||
<div className={`column info`}>
|
||||
<Info className={`h-4 w-4`} />
|
||||
{t("buy.gpt4-tip")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`tip`}>
|
||||
<Button variant={`outline`} asChild>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
closeDialog,
|
||||
dialogSelector,
|
||||
enterpriseSelector,
|
||||
expiredSelector,
|
||||
@ -21,6 +22,10 @@ import { useTranslation } from "react-i18next";
|
||||
import { useToast } from "@/components/ui/use-toast.ts";
|
||||
import React from "react";
|
||||
import "@/assets/pages/subscription.less";
|
||||
import {
|
||||
openDialog as openQuotaDialog,
|
||||
dialogSelector as quotaDialogSelector,
|
||||
} from "@/store/quota.ts";
|
||||
import {
|
||||
BookText,
|
||||
Building2,
|
||||
@ -175,6 +180,8 @@ function SubscriptionDialog() {
|
||||
const usage = useSelector(usageSelector);
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
|
||||
const quota = useSelector(quotaDialogSelector);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
useEffectAsync(async () => {
|
||||
if (!auth) return;
|
||||
@ -194,6 +201,14 @@ function SubscriptionDialog() {
|
||||
<DialogTitle>{t("sub.dialog-title")}</DialogTitle>
|
||||
<DialogDescription asChild>
|
||||
<div className={`sub-wrapper`}>
|
||||
<p
|
||||
className={`link`}
|
||||
onClick={() =>
|
||||
quota ? dispatch(closeDialog()) : dispatch(openQuotaDialog())
|
||||
}
|
||||
>
|
||||
{t("sub.quota-link")}
|
||||
</p>
|
||||
{subscription && (
|
||||
<div className={`sub-row`}>
|
||||
<div className={`sub-column`}>
|
||||
|
@ -14,7 +14,7 @@ const resources = {
|
||||
login: "登录",
|
||||
"login-require": "您需要登录才能使用此功能",
|
||||
logout: "登出",
|
||||
quota: "配额",
|
||||
quota: "点数",
|
||||
"try-again": "重试",
|
||||
"invalid-token": "无效的令牌",
|
||||
"invalid-token-prompt": "请重试。",
|
||||
@ -75,7 +75,7 @@ const resources = {
|
||||
chat: {
|
||||
web: "联网搜索",
|
||||
"web-aria": "切换网络搜索功能",
|
||||
placeholder: "写点什么...",
|
||||
placeholder: "写点什么... (Ctrl+Enter 发送)",
|
||||
recall: "历史复原",
|
||||
"recall-desc": "检测到您上次有未发送的消息,已经为您恢复。",
|
||||
"recall-cancel": "取消",
|
||||
@ -88,7 +88,7 @@ const resources = {
|
||||
restart: "重新回答",
|
||||
"copy-area": "复制选中区域",
|
||||
},
|
||||
"quota-description": "消息的配额支出",
|
||||
"quota-description": "消息的点数支出",
|
||||
buy: {
|
||||
choose: "选择一个金额",
|
||||
other: "其他",
|
||||
@ -128,6 +128,8 @@ const resources = {
|
||||
},
|
||||
sub: {
|
||||
title: "订阅",
|
||||
"quota-link": "寻求弹性计费?购买点数",
|
||||
"subscription-link": "寻求固定计费?订阅计划",
|
||||
"dialog-title": "订阅计划",
|
||||
free: "免费版",
|
||||
"free-price": "永久免费",
|
||||
@ -412,7 +414,7 @@ const resources = {
|
||||
chat: {
|
||||
web: "Web Searching",
|
||||
"web-aria": "Toggle web searching feature",
|
||||
placeholder: "Write something...",
|
||||
placeholder: "Write something... (Ctrl+Enter to send)",
|
||||
recall: "History Recall",
|
||||
"recall-desc":
|
||||
"Detected that you have unsent messages last time, has been restored for you.",
|
||||
@ -467,6 +469,8 @@ const resources = {
|
||||
},
|
||||
sub: {
|
||||
title: "Subscription",
|
||||
"quota-link": "Seeking flexible billing? Buy points",
|
||||
"subscription-link": "Seeking fixed billing? Subscribe",
|
||||
"dialog-title": "Subscription Plan",
|
||||
free: "Free",
|
||||
"free-price": "Free Forever",
|
||||
@ -762,7 +766,7 @@ const resources = {
|
||||
chat: {
|
||||
web: "Веб-поиск",
|
||||
"web-aria": "Переключить веб-поиск",
|
||||
placeholder: "Напишите что-нибудь...",
|
||||
placeholder: "Напишите что-нибудь... (Ctrl+Enter для отправки)",
|
||||
recall: "История",
|
||||
"recall-desc":
|
||||
"Обнаружено, что у вас есть неотправленные сообщения в прошлый раз, они были восстановлены для вас.",
|
||||
@ -818,6 +822,8 @@ const resources = {
|
||||
},
|
||||
sub: {
|
||||
title: "Подписка",
|
||||
"quota-link": "Ищете гибкую тарификацию? Купить очки",
|
||||
"subscription-link": "Ищете фиксированную тарификацию? Подписаться",
|
||||
"dialog-title": "Подписка",
|
||||
free: "Бесплатно",
|
||||
"free-price": "Бесплатно навсегда",
|
||||
|
1
go.mod
1
go.mod
@ -29,6 +29,7 @@ require (
|
||||
github.com/bytedance/sonic v1.10.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/webp v1.1.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/cloudwego/hertz/cmd/hz v0.7.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -100,6 +100,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
|
@ -2,9 +2,14 @@ package utils
|
||||
|
||||
import (
|
||||
"chat/globals"
|
||||
"github.com/chai2010/webp"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"math"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
@ -19,9 +24,28 @@ func NewImage(url string) (*Image, error) {
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
img, _, err := image.Decode(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
var img image.Image
|
||||
suffix := strings.ToLower(path.Ext(url))
|
||||
switch suffix {
|
||||
case ".png":
|
||||
if img, _, err = image.Decode(res.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case ".jpg", ".jpeg":
|
||||
if img, err = jpeg.Decode(res.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "webp":
|
||||
if img, err = webp.Decode(res.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "gif":
|
||||
ticks, err := gif.DecodeAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img = ticks.Image[0]
|
||||
}
|
||||
|
||||
return &Image{Object: img}, nil
|
||||
|
Loading…
Reference in New Issue
Block a user